Linux で Bash スクリプトのエラーをトラップする方法

公開: 2022-08-27
bash プロンプトを表示している Linux ラップトップ
Fatmawati achmad zaenuri/Shutterstock.com

デフォルトでは、Linux の Bash スクリプトはエラーを報告しますが、実行を続けます。 次に何が必要かを判断できるように、自分でエラーを処理する方法を示します。

スクリプトでのエラー処理

エラー処理はプログラミングの一部です。 完璧なコードを書いたとしても、エラー状態に陥ることがあります。 ソフトウェアのインストールとアンインストール、ディレクトリの作成、アップグレードと更新の実行に伴い、コンピュータの環境は時間の経過とともに変化します。

PowerShell でのパラメーター検証エラーのカスタマイズ
関連PowerShell でのパラメータ検証エラーのカスタマイズ

たとえば、以前は問題なく実行されていたスクリプトが、ディレクトリ パスが変更されたり、ファイルのアクセス許可が変更されたりすると、問題が発生する可能性があります。 Bash シェルのデフォルト アクションは、エラー メッセージを出力し、スクリプトの実行を続行することです。 これは危険なデフォルトです。

失敗したアクションが、スクリプト内で後で発生する他の処理またはアクションにとって重要である場合、その重要なアクションは成功しません。 それがどれほど悲惨なことになるかは、スクリプトが何をしようとしているのかによって異なります。

より堅牢なスキームは、エラーを検出し、シャットダウンするか障害状態を修復する必要がある場合にスクリプトを機能させます。 たとえば、ディレクトリまたはファイルが見つからない場合、スクリプトでそれらを再作成するだけで十分な場合があります。

スクリプトで回復できない問題が発生した場合は、スクリプトをシャットダウンできます。 スクリプトをシャットダウンする必要がある場合、一時ファイルを削除したり、エラー状態とシャットダウン理由をログ ファイルに書き込んだりするなど、必要なクリーンアップを実行する機会があります。

終了ステータスの検出

コマンドとプログラムは、終了時にオペレーティング システムに送信される値を生成します。 これは終了ステータスと呼ばれます。 エラーが発生しなかった場合は値がゼロになり、エラーが発生した場合はゼロ以外の値になります。

スクリプトが使用するコマンドの終了ステータス (リターン コードとも呼ばれます) を確認し、コマンドが成功したかどうかを判断できます。

Bash では、ゼロは true に相当します。 コマンドからの応答が true 以外の場合、問題が発生したことがわかり、適切なアクションを実行できます。

このスクリプトをエディターにコピーし、「bad_command.sh」という名前のファイルに保存します。

 #!/ビン/バッシュ

if ( ! bad_command ); それから
  echo "bad_command はエラーのフラグを立てました。"
  1番出口
フィ

chmodコマンドでスクリプトを実行可能にする必要があります。 これは、スクリプトを実行可能にするために必要な手順です。そのため、自分のマシンでスクリプトを試してみたい場合は、スクリプトごとに忘れずに実行してください。 いずれの場合も、適切なスクリプトの名前に置き換えてください。

 chmod +x bad_command.sh 

chmod を使用してスクリプトを実行可能にする

スクリプトを実行すると、予想されるエラー メッセージが表示されます。

 ./bad_command.sh 

コマンドの終了ステータスをチェックして、エラーが発生したかどうかを判断する

「bad_command」などのコマンドはなく、スクリプト内の関数の名前でもありません。 実行できないので、レスポンスはゼロではありません。 応答がゼロでない場合 (感嘆符はここでは論理NOT演算子として使用されます)、 ifステートメントの本体が実行されます。

実際のスクリプトでは、この例のようにスクリプトが終了するか、障害状態の修復を試みる可能性があります。

exit 1行が冗長であるように見える場合があります。 結局、スクリプトには他に何もなく、とにかく終了します。 しかし、 exitコマンドを使用すると、終了ステータスをシェルに戻すことができます。 このスクリプトが 2 番目のスクリプト内から呼び出された場合、その 2 番目のスクリプトは、このスクリプトでエラーが発生したことを認識します。

コマンドの終了ステータスで論理OR演算子を使用し、最初のコマンドからゼロ以外の応答がある場合は、スクリプト内の別のコマンドまたは関数を呼び出すことができます。

 コマンド_1 || コマンド_2

これは、最初のコマンドが実行されるか、2 ORのコマンドが実行されるため機能します。 一番左のコマンドが最初に実行されます。 成功した場合、2 番目のコマンドは実行されません。 ただし、最初のコマンドが失敗すると、2 番目のコマンドが実行されます。 したがって、このようなコードを構造化できます。 これは「logical-or./sh」です。

 #!/ビン/バッシュ

error_handler()
{
  echo "エラー: ($?) $1"
  1番出口
}

|| 悪いコマンド || error_handler "bad_command が失敗しました。行: ${LINENO}"

error_handlerという関数を定義しました。 これにより、失敗したコマンドの終了ステータスが出力され、変数$?に保持されます。 関数が呼び出されたときに渡されるテキスト行。 これは変数$1に保持されます。 この関数は、終了ステータス 1 でスクリプトを終了します。

スクリプトはbad_commandを実行しようとしますが、これは明らかに失敗するため、論理OR演算子の右側のコマンド|| 、実行されます。 これはerror_handler関数を呼び出し、失敗したコマンドの名前と、失敗したコマンドの行番号を含む文字列を渡します。

スクリプトを実行してエラー ハンドラー メッセージを確認し、echo を使用してスクリプトの終了ステータスを確認します。

 ./論理または.sh
 エコー $? 

論理 OR 演算子を使用してスクリプトでエラー ハンドラーを呼び出す

小さなerror_handler関数は、 bad_commandの実行試行の終了ステータス、コマンドの名前、および行番号を提供します。 これは、スクリプトをデバッグするときに役立つ情報です。

スクリプトの終了ステータスは 1 です。 error_handlerによって報告される 127 終了ステータスは、「コマンドが見つからない」ことを意味します。 必要に応じて、それをexitコマンドに渡すことで、スクリプトの終了ステータスとして使用できます。

別の方法として、 error_handlerを展開して、終了ステータスのさまざまな値をチェックし、それに応じてさまざまなアクションを実行する方法があります。次のような構造を使用します。

 exit_code=$?

if [ $exit_code -eq 1 ]; それから
  echo "操作は許可されていません"

elif [ $exit_code -eq 2 ]; それから
  echo "シェルビルトインの誤用"
.
.
.
elif [ $status -eq 128 ]; それから
  echo "無効な引数"
フィ

set を使用して終了を強制する

エラーが発生するたびにスクリプトを終了させたいことがわかっている場合は、強制的に終了させることができます。 これは、エラーを検出するとすぐにスクリプトが終了するため、クリーンアップの可能性やそれ以上の損害を逃すことを意味します。

これを行うには、 -e (エラー) オプションを指定してsetコマンドを使用します。 これにより、コマンドが失敗するかゼロより大きい終了コードが返されるたびに終了するようにスクリプトが指示されます。 また、 -Eオプションを使用すると、エラーの検出とトラップがシェル関数で確実に機能します。

Linux の Bash スクリプトで set と pipefail を使用する方法
関連Linux の Bash スクリプトで set と pipefail を使用する方法

初期化されていない変数もキャッチするには、 -u (未設定) オプションを追加します。 パイプされたシーケンスでエラーが検出されるようにするには、 -o pipefailオプションを追加します。 これがないと、コマンドのパイプ シーケンスの終了ステータスは、シーケンスの最後のコマンドの終了ステータスになります。 パイプ シーケンスの途中で失敗したコマンドは検出されません。 -o pipefailオプションは、オプションのリストに含まれている必要があります。

スクリプトの先頭に追加するシーケンスは次のとおりです。

 set -Eeuo pipefail

これは、「unset-var.sh」と呼ばれる短いスクリプトで、未設定の変数が含まれています。

 #!/ビン/バッシュ

set -Eeou パイプフェイル

echo "$unset_variable"

echo "この行が見えますか?"

スクリプトを実行すると、unset_variable が初期化されていない変数として認識され、スクリプトが終了します。

 ./unset-var.sh 

スクリプトで set コマンドを使用して、エラーが発生した場合にスクリプトを終了する

2 番目のechoコマンドは実行されません。

エラーのあるトラップの使用

Bash trap コマンドを使用すると、特定のシグナルが発生したときに呼び出されるコマンドまたは関数を指定できます。 通常、これは、Ctrl+C キーの組み合わせを押したときに発生するSIGINTなどのシグナルをキャッチするために使用されます。 このスクリプトは「sigint.sh」です。

 #!/ビン/バッシュ

trap "echo -e '\nTerminated by Ctrl+c'; exit" SIGINT

カウンター=0

真実でありながら
行う 
  echo "ループ番号:" $((++カウンター))
  睡眠 1
終わり

trapコマンドには、 echoコマンドとexitコマンドが含まれています。 SIGINTが発生したときにトリガーされます。 スクリプトの残りの部分は単純なループです。 スクリプトを実行して Ctrl+C を押すと、 trap定義からのメッセージが表示され、スクリプトが終了します。

 ./sigint.sh 

スクリプトでトラップを使用して Ctrl+c をキャッチする

ERRシグナルでtrapを使用して、発生したエラーをキャッチできます。 これらは、コマンドまたは関数に渡すことができます。 これが「trap.sh」です。 error_handlerという関数にエラー通知を送信しています。

 #!/ビン/バッシュ

トラップ 'error_handler $? $LINENO' エラー

error_handler() {
  echo "エラー: ($1) が $2 で発生しました"
}

主要() {
  echo "main() 関数の内部"
  悪いコマンド
  2番目
  三番
  $ を終了しますか?
}

2番目() {
  echo "main() の呼び出し後"
  echo "second() 関数の内部"
}

三番() {
  echo "thirth() 関数の内部"
}

主要

スクリプトの大部分は、 secondthirdの関数を呼び出すmain関数内にあります。 エラーが発生すると (この場合はbad_commandが存在しないため)、 trapステートメントはエラーをerror_handler関数に送信します。 失敗したコマンドの終了ステータスと行番号をerror_handler関数に渡します。

 ./trap.sh 

ERR でトラップを使用してスクリプト内のエラーをキャッチする

error_handler関数は、ターミナル ウィンドウにエラーの詳細を表示するだけです。 必要に応じて、 exitコマンドを関数に追加して、スクリプトを終了させることができます。 または、一連のif/elif/fiステートメントを使用して、さまざまなエラーに対してさまざまなアクションを実行できます。

一部のエラーを修正できる場合もあれば、スクリプトを停止する必要がある場合もあります。

最後のヒント

エラーをキャッチすることは、多くの場合、問題が発生する可能性があることを先取りし、発生した場合にそれらの不測の事態を処理するコードを組み込むことを意味します。 これは、スクリプトの実行フローと内部ロジックが正しいことを確認することに加えて行われます。

このコマンドを使用してスクリプトを実行すると、Bash はスクリプトの実行時にトレース出力を表示します。

 bash -x your-script.sh

Bash は、ターミナル ウィンドウにトレース出力を書き込みます。 各コマンドとその引数が表示されます (引数がある場合)。 これは、コマンドが展開された後、実行される前に発生します。

とらえどころのないバグを追跡するのに非常に役立ちます。

関連: Linux Bash スクリプトを実行する前に構文を検証する方法