Bashスクリプトが失敗したときの体系的なトラブルシューティングアプローチ

終了コードの確認、コマンドのトレース、環境問題の切り分け、無人実行のログ記録により、Bashスクリプトのデバッグを行います。

Bashスクリプトが失敗したときの体系的なトラブルシューティングアプローチ

Bashスクリプトの失敗トラブルシューティングは、コマンドが間違っていたのか、環境が異なっていたのか、入力が期待と違っていたのかという問いから始まります。再現可能なデバッグルーチンがあれば、cronジョブ、デプロイフック、バックアップスクリプトが壊れたときに推測する必要がなくなります。

以下の手順を使用して、失敗のシグナルを読み取り、Bashが実際に実行した内容をトレースし、問題を特定のコマンドに絞り込みます。


フェーズ1: 準備と初期評価

複雑なデバッグフラグに飛び込む前に、基本要素が整っていることを確認してください。構造化された初期評価は、かなりの時間を節約します。

1. エラーメッセージと終了コードの確認

最も直接的な手がかりは、シェルが報告するエラーメッセージです。提供されている場合は、記載された行番号に特に注意してください。

  • 終了コード: シェルスクリプトでは、特殊変数 $? に最後に実行されたフォアグラウンドコマンドの終了ステータスが保持されます。成功したコマンドは 0 を返します。ゼロ以外の値は失敗を示します。

    some_command
    echo "コマンドはステータス $? で終了しました"
    # $? が 127 の場合、多くの場合「コマンドが見つかりません」を意味します。
    

2. スクリプト実行モードの確認

スクリプトが意図した方法で実行されていることを確認します。特に、シバン行で指定されたインタプリタに関して重要です。

  • シバン: インタプリタを定義するために、スクリプトは常に適切なシバン行で始めてください。#!/bin/bash が標準ですが、移植性のために #!/usr/bin/env bash が好まれることもよくあります。

  • パーミッション: スクリプトに実行権限が設定されていることを確認します。

    chmod +x your_script.sh
    

3. 実行環境の切り分け

環境の違いは、断続的な障害の主な原因です。スクリプトが実行されるべき環境で常にテストするか、開発環境と本番環境で異なる変数を確認してください。

  • 直接テスト: 名前だけで実行する場合の潜在的なPATH問題を回避するために、インタプリタを使用してスクリプトを直接実行します。

    /bin/bash ./your_script.sh
    

フェーズ2: Bashデバッグフラグの有効化

Bashには、実行フローと変数評価をトレースできる強力な組み込みフラグが用意されており、ロジックエラーや予期しない展開を特定するのに重要です。

1. 基本的なデバッグフラグ

これらのフラグは通常、シバン行に追加するか、スクリプト内で set を使用して有効/無効にします。

フラグ コマンド 目的
-n set -n コマンドを読み込むが実行しない(構文チェックのみ)。
-v set -v シェル入力行を読み込んだまま出力する(詳細モード)。
-x set -x コマンドとその引数を実行時に出力する(トレースモード)。これはロジックエラーに最も強力です。

2. トレースモード(set -x)の使用

set -x は、実行されたすべてのコマンドの出力の前に + 記号を付け、変数展開を含め、Bashが解釈している内容を正確に表示します。

トレースの例:

不適切なクォーティングが原因で失敗するスクリプトを考えます。

# 元のスクリプトスニペット
USER_INPUT="Hello World"
echo $USER_INPUT  # USER_INPUTにスペースが含まれ、別のコマンドに渡された場合に失敗

set -x を有効にして実行した場合(#!/bin/bash -x またはスクリプト先頭の set -x 経由):

+ USER_INPUT='Hello World'
+ echo Hello World
Hello World

クォーティングの問題が疑われる場合は、問題のあるセクションの周りでトレースモードを選択的に有効にできます。

set -x
# 正常に動作するコマンド

# 問題のあるセクションのみをトレース
COMMAND_THAT_FAILS_DUE_TO_EXPANSION
set +x
# スクリプトの残り

ベストプラクティス: スクリプト全体をデバッグするには、#!/bin/bash -x を使用するか、シバンの直後に set -x を配置します。

3. 変数展開のデバッグ

多くの失敗は、変数がどのように展開されるか(または展開されないか)に起因します。変数の周りには常に二重引用符("$VAR")を多用してワード分割やグロブ展開を防ぎますが、トレース(set -x)を使用して展開が期待通りに行われているかを確認します。

空白を含む変数のリテラル値を確認したい場合は、引用符で囲み、区切り文字で囲んでエコーできます。

VAR="a b c"
printf '[%s]\n' "$VAR"
# 出力: [a b c]

フェーズ3: 一般的なエラータイプの処理

デバッグフラグが有効になると、エラーは通常、予測可能なカテゴリに分類されます。

1. コマンドが見つからない(終了コード127)

このエラーは、多くの場合 your_command: command not found として表示され、シェルが実行可能ファイルを見つけられないことを示します。

  • PATHの確認: コマンドを含むディレクトリが、スクリプトの実行コンテキスト内の $PATH 環境変数にリストされていることを確認します。
  • 絶対パスの使用: 不明な場合は、コマンドのフルパスを使用します(例:curl の代わりに /usr/bin/curl)。

2. 構文エラー

これらは多くの場合、一致しない区切り文字、制御構造(ifforwhile)の誤った使用、またはセミコロン/改行の欠落が含まれます。

  • set -n(実行なし): スクリプトを set -n で実行すると、Bashはすべてを実行せずに解析するため、閉じられていない括弧や欠落した fi/done ステートメントがすぐに明らかになります。

  • 条件構文: [[ ... ]][ ... ] の違いに注意してください。たとえば、算術演算のテストには (( ... )) または let が必要であり、標準のテスト構造は使用しません。

    例(算術コンテキスト):

    # AがBより大きいかどうかを確認する正しい方法
    A=10
    B=5
    if (( A > B )); then
        echo "AはBより大きい"
    fi
    

3. パーミッションと入出力の問題

スクリプトは実行されるが、ファイルや外部プロセスとの対話で失敗する場合は、パーミッションとファイル記述子を確認します。

  • 入力リダイレクション: ファイルから入力をリダイレクトしている場合は、そのファイルが存在し、読み取り可能であることを確認します。

  • 出力リダイレクション: 出力先のディレクトリが存在し、スクリプトユーザーに書き込み権限があるかどうかを確認します。

    SUDOに関する警告: sudo でスクリプトを実行すると、$PATH などの環境変数やユーザー固有の設定(.bashrc など)がリセットまたは変更されることがよくあります。通常のユーザーとして実行したときに機能するコマンドが、コンテキストやパスの欠落により sudo では失敗する可能性があります。

フェーズ4: ログ記録とシステムチェック

バックグラウンドで実行されるスクリプト(例:Cron経由)の場合、直接の端末出力は利用できません。堅牢なログ記録が不可欠です。

1. デバッグ用の出力リダイレクション

無人実行時には、標準出力(stdout、ディスクリプタ1)と標準エラー出力(stderr、ディスクリプタ2)の両方をログファイルにリダイレクトします。これらを組み合わせるのが一般的です。

# すべての出力をdebug.logにリダイレクト
./your_script.sh >> debug.log 2>&1

set -x を使用している場合、トレース出力も同じログファイルに送られ、実行フローとエラーの完全な記録が提供されます。

2. システムヘルスの確認

スクリプト自体は問題ないが、システム環境に問題がある場合があります。

  • ディスク容量: システムのディスク容量が不足していませんか(df -h)?これにより、書き込み操作が停止します。
  • メモリ: メモリ使用量を確認します(free -m)。メモリプレッシャーが高いと、外部コマンドが失敗したりハングしたりする可能性があります。
  • Cron環境: Cronを介してスケジュールされている場合、Cronジョブは非常に制限された環境で実行されることに注意してください。Cronジョブのセットアップで保証されていない場合は、スクリプトの先頭で必要な環境変数を常に明示的に定義してください。

デバッグパスを短く保つ

Bashスクリプトが失敗した場合、まず終了コードと正確なエラーをキャプチャします。次に、シバン、パーミッション、$PATH、入力ファイル、ユーザーコンテキストを確認します。原因がまだ不明な場合は、疑わしいブロックの周りでのみ set -x を有効にし、無人実行の出力をログに送信します。

この順序により、トラブルシューティングが実用的になります。最も明確な証拠から最も詳細なトレースに移行し、ノイズに圧倒されることなく進められます。