応用編:エラー処理・デバッグと安全な実行
Bash スクリプトの暴走やサイレント障害を防ぐ厳格モード(set -euo pipefail)、trap によるエラー捕捉、および効果的なデバッグ手法を解説します。
デフォルトの Bash が抱える危険性
Bash は既定の状態では以下のような挙動を示します。
- コマンドの実行が途中で失敗しても、何事もなかったかのように後続の処理を継続する
- 誤字などによる未定義の変数を参照しても、単に空文字として処理を続行する
- パイプラインの途中のコマンドがエラーになっても、最後のコマンドが成功すれば成功とみなす
例えば rm -rf "$TARGET_DIR/*" において $TARGET_DIR が未定義の場合、空文字とみなされてルートディレクトリ直下(/*)が削除されるといった重大な事故に直結します。
厳格モード(set -euo pipefail)
スクリプトの冒頭で以下のオプションを設定することで、安全な実行環境(厳格モード)を確立します。
#!/usr/bin/env bash
set -euo pipefail
| オプション | 略称 | 役割 |
|---|---|---|
set -e | errexit | コマンドが1つでも失敗(終了ステータスが非0)したら即座にスクリプトを中断する |
set -u | nounset | 未定義の変数を参照した場合、エラーを表示してスクリプトを中断する |
set -o pipefail | - | パイプライン内で1つでも失敗したコマンドがあれば、パイプライン全体を失敗とする |
» 意図したエラーの許容
set -e 環境下で、特定コマンドのエラー(終了コード非0)を無視して継続したい場合は、|| true を付与するか if 文で判定します。
# grep で検索結果が0件(終了コード 1)でもスクリプトを停止させない
grep "optional_setting" config.txt || true
trap によるエラーの補足と通知
trap コマンドを使用すると、スクリプト内でエラー(ERR)が発生した瞬間に指定した関数を自動実行できます。組み込み変数 $LINENO(行番号)や $BASH_COMMAND(失敗したコマンド)を記録するのに役立ちます。
#!/usr/bin/env bash
set -euo pipefail
# エラー発生時のハンドラ関数
on_error() {
local exit_code="$1"
local line_no="$2"
local command="$3"
echo "[ERROR] Command '$command' failed at line $line_no with exit code $exit_code" >&2
}
# ERR シグナルをフック
trap 'on_error $? $LINENO "$BASH_COMMAND"' ERR
# 意図的にエラーを起こすテスト
ls /path/to/missing_file
デバッグ手法とトレース実行
スクリプトの不具合を素早く調査・特定するための技法です。
» 構文チェック(-n オプション)
スクリプトを実行せずに、文法エラー(if の閉じ忘れ等)の有無だけを静的に検証します。
bash -n script.sh
» 実行トレース(set -x)
実行される各コマンドとその引数を、実際に評価された値に展開した状態でターミナルに出力します。
# スクリプト全体をデバッグ実行
bash -x script.sh
# スクリプト内の特定ブロックだけトレースを有効化
set -x
critical_processing
set +x # トレースを停止
» 構造化されたログ出力
標準出力と標準エラー出力を適切に分離し、タイムスタンプを付与したログ関数を用意します。
log_info() { echo "[INFO] $(date +%T) - $*"; }
log_warn() { echo "[WARN] $(date +%T) - $*" >&2; }
log_error() { echo "[ERROR] $(date +%T) - $*" >&2; }