Shell の基礎知識

Shellスクリプト中級者向けガイド|実用的なシェルの書き方とコツ

初心者の次のステップ

シェルスクリプトを学び始めたエンジニアの皆さん、基礎的な構文や基本操作に慣れてきた今、次に進むべきは「基礎を超えた一歩」です。シェルスクリプトは、単なるコマンドの集合体ではなく、自動化や効率化を実現するための強力なツールです。しかし、その真価を発揮するには、デバッグの方法やエラーハンドリング、リダイレクトの活用など、基礎の次に覚えるべき実践的なスキルが欠かせません。

この記事では、初心者の次のステップとして、シェルスクリプトをさらに使いこなすための重要なテクニックやベストプラクティスをわかりやすく解説します。これらを身につけることで、より洗練されたスクリプトを書き、システム管理や開発の効率を大幅に向上させることができるでしょう。

さあ、一歩先のスキルセットを習得して、シェルスクリプトを日常の強力な味方にしていきましょう!

シェルスクリプトのデバッグ方法

シェルスクリプトを作成すると、うまく動作しないことがあります。そんなときに役立つのが「デバッグ」です。デバッグとは、スクリプトがどう動いているかを確認して、エラーや問題箇所を見つけることです。ここでは、デバッグを簡単に行う方法を紹介します。

スクリプト全体をデバッグする方法

スクリプト全体の動きを確認するには、-x オプションを使います。これを使うと、スクリプトの中で実行される各コマンドが表示されます。

例えば、以下のスクリプト sample.sh をデバッグしてみます。

#!/bin/bash

echo "スクリプト開始"
ls /not_exist
echo "スクリプト終了"

このスクリプトを次のように実行します。

bash -x sample.sh

すると、以下のようにコマンドが実行されるたびにその内容が表示されます。

+ echo スクリプト開始
スクリプト開始
+ ls /not_exist
ls: cannot access '/not_exist': No such file or directory
+ echo スクリプト終了
スクリプト終了

+マークのついた行が、スクリプトの中で実行されたコマンドを示しています。
エラーの原因(ls: cannot access '/not_exist')がすぐにわかります。

スクリプトの文法チェックをする方法

スクリプトを実行せずに、文法エラーだけを確認したい場合には、-n オプションを使います。

bash -n sample.sh

このコマンドは、スクリプトに文法エラーがあればエラーを表示し、なければ何も表示しません。これにより、スクリプトを実行せずに安全にチェックできます。

スクリプトの一部だけをデバッグする方法

スクリプトの中で特定の箇所だけデバッグしたい場合には、set -x と set +x を使います。

#!/bin/bash
echo "スクリプト開始"

set -x # ここからデバッグ開始
ls /not_exist
echo "デバッグ対象の処理"
set +x # ここでデバッグ終了

echo "スクリプト終了"

このスクリプトを実行すると、以下のような出力になります。

スクリプト開始
+ ls /not_exist
ls: cannot access '/not_exist': No such file or directory
+ echo デバッグ対象の処理
デバッグ対象の処理
スクリプト終了

set -x と set +x の間にあるコマンドだけがデバッグ表示されます。
必要な箇所だけデバッグできるので便利です。

最初は bash -x を使ってスクリプト全体の動きを確認するのがおすすめです。
スクリプトが長くなったら、set -x と set +x を活用して、特定の部分だけをデバッグする方法を試してみましょう。
文法チェック (bash -n) も合わせて覚えると、実行する前にエラーを減らせます。

標準入出力とリダイレクトの活用

シェルスクリプトの「標準入出力」と「リダイレクト」は、データをファイルに保存したり、コマンド同士をつないだりするための基本的かつ強力な機能です。ここでは、初心者向けにその仕組みと使い方をわかりやすく説明します。

標準入出力って何?

まず、「標準入出力(stdin, stdout, stderr)」について簡単に理解しましょう。

  • 標準入力(stdin)
    コマンドやスクリプトが「受け取る」データの流れ。
    通常はキーボードから入力します。
  • 標準出力(stdout)
    コマンドやスクリプトが「出力する」データの流れ。
    通常は画面(ターミナル)に表示されます。
  • 標準エラー出力(stderr)
    エラー時の出力データの流れ。
    標準出力とは別に扱われ、通常は同じく画面に表示されます。

リダイレクトの基本

「リダイレクト」を使うと、これらの入出力をファイルに保存したり、別のコマンドに渡したりすることができます。

リダイレクトについてもっと詳しく知りたい場合は、次の記事をご参照ください。

出力をファイルに保存

  • 上書き保存: >
    コマンドの出力を指定したファイルに書き込みます。
    ファイルがすでに存在する場合は上書きされるので注意!

echo "Hello, World!" > output.txt

結果: output.txt に「Hello, World!」という文字列が保存されます。

  • 追記保存: >>
    ファイルがすでに存在している場合、内容を追記します。

echo "追加の行" >> output.txt

結果: output.txt の末尾に「追加の行」が追加されます。

エラー出力を保存

  • 標準エラー出力(stderr)をファイルに保存: 2>
    エラーだけを指定したファイルに保存します。

ls /not_exist 2> error.log

結果: 存在しないディレクトリ /not_exist のエラーメッセージが error.log に保存されます。

出力とエラーを同時に保存

  • 両方の出力を1つのファイルにまとめる: > file 2>&1
    標準出力と標準エラー出力を同じファイルに保存します。

ls /exist /not_exist > output.log 2>&1

結果: output.log に成功した出力とエラーメッセージの両方が記録されます。

パイプ | の活用

「パイプ」を使うと、1つのコマンドの出力を次のコマンドの入力として渡すことができます。

ls | grep "sample"

ls の結果(ファイル一覧)を grep に渡し、「sample」という文字列を含む行だけを表示します。

標準入力を使う方法

「標準入力」を使って、コマンドがデータを受け取る仕組みも覚えましょう。

  • ファイルを入力として渡す: <
    指定したファイルをコマンドに入力します。

cat < input.txt

結果: input.txt の内容が画面に表示されます。

エラーハンドリング

エラーハンドリングは、スクリプトの実行中に発生するエラーを検出し、適切に対処するための重要なスキルです。特にシェルスクリプトでは、エラーを無視すると後続の処理が予期せぬ結果を引き起こすことがあります。ここでは、初心者でもすぐに使えるエラーハンドリングの基本を解説します。

終了ステータスの確認

シェルスクリプトでは、各コマンドの実行結果を「終了ステータス」として取得できます。

  • 成功時: 0 を返す。
  • 失敗時: 0 以外の値を返す。

基本的な使い方
終了ステータスは $? で確認できます。

mkdir /not_exist_directory
if [ $? -ne 0 ]; then
 echo "エラー: ディレクトリの作成に失敗しました"
 exit 1
fi

[ $? -ne 0 ] は「直前のコマンドが失敗した場合」を意味します。
exit 1 でスクリプト全体を終了し、呼び出し元にエラーを通知します。

自動エラーチェックの設

set -e をスクリプトの冒頭に記述すると、エラーが発生した時点でスクリプトが停止します。これにより、不具合がある状態で処理が続行されるのを防ぎます。。

#!/bin/bash

set -e
echo "ファイルをコピーします"
cp file1.txt file2.txt
echo "処理完了"

注意: set -e を使用すると、すべてのエラーでスクリプトが停止します。処理を続けたい場合は、この設定を一時的に無効化する必要があります。

必要に応じて特定のコマンドだけエラーを無視したい場合は、次のように記述します。

cp file1.txt file2.txt || true

エラー発生時の後処理

trap コマンドを使うと、スクリプトが中断されたときやエラーが発生したときに特定の処理を実行できます。

#!/bin/bash

trap 'echo "中断されました"; exit 1' INT
echo "スクリプトを実行中です…"
sleep 10
echo "処理が正常に終了しました"

上記の例では、Ctrl+C を押すと INT シグナルが送られ、「中断されました」と表示されます。

エラー時に一時ファイルやリソースを削除する処理を追加できます。

#!/bin/bash

trap 'rm -f /tmp/tempfile; echo "エラーによりクリーンアップしました"; exit 1' ERR

# 一時ファイル作成
touch /tmp/tempfile
echo "処理を開始します"
cp /not_exist /tmp/tempfile

trap を使って、エラー時に /tmp/tempfile を自動削除。

エラーハンドリングを実践する

次のスクリプトは、エラーハンドリングを組み込んだ実用的な例です。

#!/bin/bash

set -e
trap 'echo "エラーが発生しました"; exit 1' ERR

echo "バックアップを開始します"

# バックアップディレクトリ作成
mkdir -p /backup || exit 1

# ファイルをバックアップ
cp /important/file1 /backup/

echo "バックアップが完了しました"

  1. 重要な処理の直後にエラーチェックを入れる:
    $? を使うか、set -e を活用。
  2. エラー内容をユーザーに明示する:
    echo を使って、エラー原因を出力。
  3. スクリプトが途中で中断された場合の対応を考える:
    trap を活用してクリーンアップ処理を追加。

エラーハンドリングをマスターすると、シェルスクリプトが信頼性の高いものになります。失敗を未然に防ぎ、トラブルシューティングを効率化できるので、ぜひ取り入れてみてください!

シェルスクリプトのベストプラクティス

シェルスクリプトは、システム管理や自動化タスクに非常に有用です。しかし、適切な書き方をしなければ、読みにくく、トラブルの原因になることもあります。ここでは、初心者から中級者までが意識すべき「シェルスクリプトのベストプラクティス」を紹介します。

スクリプトの冒頭に設定を書く

スクリプトの一番上には、次のような情報を記載しましょう:
シェバン(Shebang): 実行するシェルを指定します。

#!/bin/bash

set オプションの活用: 安全性を高めるため、以下の設定を追加します。

set -euo pipefail

-e: エラーが発生したらスクリプトを終了。
-u: 未定義の変数を使用するとエラー。
-o pipefail: パイプ内でエラーが発生した場合にエラーを返す。

可読性を意識する

シェルスクリプトを作成する際、可読性を意識することは非常に重要です。なぜなら、スクリプトは自分だけが使うものではなく、他のエンジニアや将来の自分自身がメンテナンスする可能性があるからです。

スクリプトが複雑で読みにくいと、トラブルシューティングや改良が必要になったときに時間がかかり、場合によっては誤った修正を招くこともあります。誰が見ても理解しやすく、効率よく修正できるように書くことが、優れたスクリプトの基本です。

以下に、可読性を高めるための具体的な方法を紹介します。

コメント

スクリプトの意図や主要な処理にコメントを追加しましょう。

# ユーザーが入力したファイルをバックアップ
backup_file() {
  cp "$1" "$1.bak"
}

可読性を意識する

コードをセクションごとに分け、見出しコメントを使って整理します。

------------------------------------------------------------
変数の定義
------------------------------------------------------------
LOG_FILE="/var/log/my_script.log"

------------------------------------------------------------
関数の定義
------------------------------------------------------------
log_message() {
  echo "$(date): $1" >> $LOG_FILE
}

変数を慎重に扱う

関数内で使用する変数には、local を付けて他のスコープに影響を与えないようにします。

my_function() {
  local my_var="ローカル変数"
  echo "$my_var"
}

変数名をわかりやすくする。短すぎる名前や曖昧な名前を避けましょう。

# 良い例
backup_directory="/backup"

# 悪い例
bd="/backup"

エラーハンドリングを組み込む

エラー時に特定の処理を実行するためのハンドリングを追加します。

trap 'echo "エラー発生!クリーンアップ中…"; rm -f /tmp/tempfile' ERR

重要な処理の後にエラーをチェックします。

cp /important/file /backup/ || { echo "バックアップに失敗"; exit 1; }

再利用性を意識する

よく使う処理を関数化し、再利用性を高めましょう。

log_message() {
  echo "$(date): $1" >> /var/log/script.log
}

共通処理は別のスクリプトに切り出して読み込みます。

# 共通関数ファイルを読み込む
source /path/to/common_functions.sh

ユーザー入力に注意する

ユーザーが入力した値が正しいかどうかを確認します。

if [[ ! -f "$1" ]]; then
  echo "エラー: 入力されたファイルが存在しません"
  exit 1
fi

クォートを使って予期しない挙動を防ぎます。

rm -rf "$input_directory" # 良い例
rm -rf $input_directory # 悪い例(スペースがあると危険)

ログを活用する

スクリプトがどのように動作しているかを記録することで、デバッグや監視が容易になります。

log_message() {
  echo "$(date): $1" >> /var/log/my_script.log
}

重要度に応じて、情報を分類します。

log_error() {
  echo "$(date) [ERROR]: $1" >> /var/log/my_script.log
}
log_info() {
  echo "$(date) [INFO]: $1" >> /var/log/my_script.log
}

これらのベストプラクティスを活用することで、シェルスクリプトの可読性、信頼性、再利用性が向上します。

特に、コメントの記述、エラーハンドリング、再利用性を意識することで、他のエンジニアにも理解しやすいスクリプトを作成できるようになります。

常に「将来の自分や他の人が読んだときに理解しやすいか」を考えながらコーディングしましょう!

実践的な応用例

シェルスクリプトの基礎を学んだら、次は実際の業務や日常で役立つ応用例を試してみましょう。ここでは、シェルスクリプトを使った「実践的なタスク」の例をいくつか紹介します。それぞれの例を参考に、自分の環境に合わせて応用してみてください。

ログ管理スクリプト

!/bin/bash
set -e

# ログファイルの設定
LOG_FILE="/var/log/script.log"
ERROR_LOG="/var/log/script_error.log"

# ログを出力する関数
log_message() {
  echo "$(date +'%Y-%m-%d %H:%M:%S') [INFO]: $1" >> $LOG_FILE
}
log_error() {
  echo "$(date +'%Y-%m-%d %H:%M:%S') [ERROR]: $1" >> $ERROR_LOG
}

# メイン処理
log_message "スクリプト開始"

if cp /path/to/source/file /path/to/destination/; then
  log_message "ファイルのコピーが成功しました"
else
  log_error "ファイルのコピーに失敗しました"
  exit 1
fi

log_message "スクリプト終了"

成功ログとエラーログを分けて記録。
log_message と log_error を関数化して再利用可能にする。

データ処理スクリプト

特定のデータを抽出して整形するスクリプトです。

#!/bin/bash

# 入力ファイルと出力ファイルの設定
INPUT_FILE="data.txt"
OUTPUT_FILE="filtered_data.txt"

# データ抽出と整形
grep "重要" "$INPUT_FILE" | awk '{print $1, $3}' | sort > "$OUTPUT_FILE"
echo "データの処理が完了しました: $OUTPUT_FILE"

grep を使って必要なデータを抽出。
awk で整形し、sort で並べ替え。
入力と出力ファイルを指定して汎用性を高める。

バックアップスクリプト

ディレクトリをバックアップするスクリプトです。エラー時には通知を送る仕組みを追加しています。

#!/bin/bash

# バックアップ元とバックアップ先
SOURCE_DIR="/path/to/source"
BACKUP_DIR="/path/to/backup"

# バックアップ処理
if cp -r "$SOURCE_DIR" "$BACKUP_DIR"; then
  echo "バックアップが成功しました: $BACKUP_DIR"
else
  echo "バックアップに失敗しました" | mail -s "バックアップ失敗通知" admin@example.com
  exit 1
fi

cp -r を使ってディレクトリごとコピー。
mail コマンドでエラー通知を送信(mail がインストールされている場合)。

APIデータの取得と保存

シェルスクリプトでAPIを呼び出し、データを取得して保存する例です。

#!/bin/bash

# APIのURL
API_URL="https://api.example.com/data"
OUTPUT_FILE="api_response.json"

# データ取得
curl -s "$API_URL" > "$OUTPUT_FILE"
if [ $? -eq 0 ]; then
  echo "APIデータを正常に取得しました: $OUTPUT_FILE"
else
  echo "APIデータの取得に失敗しました"
  exit 1
fi

curl を使ってAPIにリクエストを送信。
レスポンスデータをJSON形式で保存。

これらの応用例を元に、自分の環境やニーズに合ったスクリプトを作成してみてください。実践的なタスクを繰り返し作成することで、シェルスクリプトのスキルが自然と身につきます。また、スクリプトを他人と共有する際は、コメントをしっかり書き、エラーハンドリングを忘れないようにしましょう。

よく読まれている記事

1

Shellとは? Shellとは、人間の理解できる言葉を機会へ伝えるプログラムです。 Linux環境でコマンドプロンプト画面を開いているとき、常にShellは起動している状態です。 「Shell」とは ...

2

Linuxは主にサーバー用として利用されるOSです。大規模な基幹システムの開発者、ロボットや家電開発等の組み込み系エンジニア、ネットワーク機器やデータベースに携わるインフラエンジニアは触れることが多い ...

3

プログラミング言語を習得しようと思った時、必ずと言っていいほど候補として挙げられるのが「Java」というプログラミング言語です。 「Java」は、現在日本で最も使われている言語であり、非常に人気のある ...

4

この記事は、Linuxについて勉強している初心者の方向けに「Shellスクリプト」について解説します。最後まで読んで頂けましたら、Shellスクリプトはどのような役割を担っているのか?を理解出来るよう ...

-Shell の基礎知識