🤖

bashでエラー発生箇所で処理停止させる方法

2024/04/03に公開

エラー発生箇所で処理停止させる方法

bashスクリプトは通常、
エラーが発生してもそのまま処理を継続します。

エラー発生箇所を記録してそこで処理停止するようにするためには、
bashスクリプトの先頭に以下のような設定を記載しておきます。

#!/bin/bash
# エラー時にエラーメッセージを表示して停止するように設定。
set -eu -o pipefail
trap 'EXIT_STATUS="${?}"; echo "ERROR: file = "${0}", line no = "${LINENO}", exit status = "${EXIT_STATUS}"" >&2; exit "${EXIT_STATUS}"' ERR

これにより、エラー発生箇所でエラーメッセージを表示して、そこで処理が停止するようになり、安全性が高まります。

具体例

例えば、以下のとおりのbashスクリプトファイルrun.shを作成して、

./run.sh
#!/bin/bash
# エラー時にエラーメッセージを表示して停止するように設定。
set -eu -o pipefail
trap 'EXIT_STATUS="${?}"; echo "ERROR: file = "${0}", line no = "${LINENO}", exit status = "${EXIT_STATUS}"" >&2; exit "${EXIT_STATUS}"' ERR

# 絶対に失敗するコマンド
cat /

# ここまで実行されずに、エラー発生箇所で処理が停止される。
echo "successfully completed!"

以下のようにしてrun.shを実行すると、

# 実行権限を付与
chmod 700 run.sh
# 実行
./run.sh

以下のようなエラーメッセージが表示されて、エラー発生箇所で処理が停止することを確認できます。

cat: /: Is a directory
ERROR: file = ./run.sh, line no = 7, exit status = 1

補足説明

上記の設定の補足説明としては、
setコマンドの
-eオプションで、パイプやサブシェルで実行したコマンドが1つでもエラーになったら直ちにシェルを終了するようにし、
-uオプションで、パラメーター展開中に、設定していない変数があったらエラーとする(特殊パラメーターである@*は除く)ようにし、
-o pipefailオプションでパイプラインの返り値を、全部EXIT STATUSが0なら0、1つでもEXIT STATUSが0でないものがあるなら最後の0以外のEXIT STATUSにしています。
その他のsetコマンドのオプションについては以下のサイトを参照してください。
https://atmarkit.itmedia.co.jp/ait/articles/1805/10/news023.html

また、trapコマンドでエラー発生時に実行するスクリプトを定義しています。
実行したいスクリプトを引数としてそのまま渡すために(引数として渡す際の変数展開などを避けるために)、
シングルクォーテーションで囲っています。
具体的には、シングルクォーテーションで囲まれた、以下のとおりのスクリプトがエラー発生時に実行されます。

EXIT_STATUS="${?}"; echo "ERROR: file = "${0}", line no = "${LINENO}", exit status = "${EXIT_STATUS}"" >&2; exit "${EXIT_STATUS}"

;で各コマンドを区切っています。
変数${?}には、エラー発生箇所のコマンドのEXIT STATUSが記録されています。
これを一旦変数${EXIT_STATUS}に格納しています。
理由としては、変数${?}の値は、コマンドの実行によって変化してしまうためです。
そのため、最初のコマンド

EXIT_STATUS="${?}";

で変数${EXIT_STATUS}に格納して保存しています。
(このコマンドの実行完了直後に、変数${?}の値は、このコマンドのEXIT STATUSに変化してしまいます。)

その後、

echo "ERROR: file = "${0}", line no = "${LINENO}", exit status = "${EXIT_STATUS}"" >&2;

echoコマンドにより、エラー発生箇所を出力しています。
より詳細に補足すると、このコマンドの末尾の>&2により、
エラー発生箇所のメッセージを標準エラー出力にリダイレクトして出力しています。

変数${0}は、実行中のスクリプトファイル名、
変数${LINENO}は、実行中の行数、
変数${EXIT_STATUS}は、先ほど格納済みのEXIT STATUSです。

最後のコマンド

exit "${EXIT_STATUS}"

で、最初のコマンドで保存しておいた、エラー発生箇所のコマンドのEXIT STATUSで処理を終了させます。

Discussion