はじめに
CI などのマネージドサービス上でシェルスクリプトを動かしている場合、スクリプトの実行に失敗したにもかかわらず、表示上は Success となっているような不具合に遭遇することがあります。
原因を調査してみると、 外部コマンドの失敗時に、シェルスクリプトがエラーコードを返していない ことが原因だったりします。
要はシェルスクリプトのエラーハンドリングが適切にされていないことが原因なのですが、かといって、毎回コマンド実行の末尾に .. command .. || exit 1
を書くのは面倒ですし、コードの可読性が低下してしまいます。
シェルスクリプト中の外部コマンドの実行に失敗したときに、呼び出し側のスクリプトの処理を中断し、エラーコードを返す ことができると楽ですよね。
set
コマンドを使って bash にこのような振る舞いをさせることができます。
サンプルスクリプト
例があるほうがわかりやすいと思いますので、以下のようなスクリプトを使って説明します。
task.sh
#!/bin/bash
./hello.sh
echo success!
exit 0
hello.sh
#!/bin/bash
echo "hello!"
exit 1 # 明示的に「失敗」を表すエラーコードを返す
サンプルスクリプトの実行結果
task.sh を実行した結果は、以下のように「成功」となります。
bash-5.0$ ./task.sh
hello!
success!
bash-5.0$ echo $?
0
外部コマンドの処理失敗時にエラーコードを返す
論理演算子 ||
を使って、 コマンドの実行が失敗した場合にエラーコードを返す ことができます。
.. command .. || exit 1
シェルスクリプト
task.sh
#!/bin/bash
./hello.sh || exit 1
# ..後続の処理はそのまま..
実行結果
bash-5.0$ ./task.sh
hello!
bash-5.0$ echo $?
1
bash のデフォルトの振る舞いを変更する
毎回コマンド実行の末尾に .. command .. || exit 1
を書く方法でもやりたいことは実現できますが、書き漏らしてしまう可能性があります。また、コードの可読性が低下してしまいます。
シェルスクリプト中の外部コマンドの実行に失敗したときに、呼び出し側のスクリプトの処理を中断し、エラーコードを返す ことができると楽ですよね。
set
コマンドを使って bash にこのような振る舞いをさせることができます。
外部コマンドの失敗時に即座にエラーコードを返す
set -e
というコマンドを実行すると、外部コマンドの実行に失敗に、直ちにエラーコードを返す ことができます。
- -e: スクリプト中で失敗した場合にただちにエラーコードを返す
set -e
より詳細な説明は参考資料[1]を参照してください。
シェルスクリプト
task.sh
#!/bin/bash
set -e
# .. 後続の処理はそのまま ..
hello.sh はそのまま。
実行結果
実行結果は「失敗」になります。
また、 hello.sh の処理に失敗した後、後続の処理(echo success!
)が実行されていないのもわかります。
bash-5.0$ ./task.sh
hello!
bash-5.0$ echo $?
1
パイプラインの途中で処理に失敗した場合の対応
set -e
を指定することで、外部コマンドの失敗時のエラーハンドリングをサボることができました。
ところが、外部コマンドの結果をパイプラインで処理させている場合は想定どおりの振る舞いをしないことがわかります。
シェルスクリプト
hello.sh の実行結果を grep
してみます。
task.sh
#!/bin/bash
set -e
./hello.sh | grep hello
# .. 後続の処理はそのまま ..
実行結果
task.sh の実行結果は意に反して「成功」となります。
bash-5.0$ ./task.sh
hello!
success!
bash-5.0$ echo $?
0
パイプの途中で処理に失敗した場合も適切に処理する
パイプの途中で失敗した場合にも、 「失敗」と判断させたいです。
これも set
を使ってでできます。
- -o pipefail: パイプラインの途中で失敗した場合、パイプライン全体のコマンドの結果を失敗したコマンドのエラーコードにする
-o pipefail
シェルスクリプト
task.sh
#!/bin/bash
set -e
set -o pipefail
./hello.sh | grep hello
echo success!
exit 0
実行結果
パイプラインの途中で失敗した場合も、「失敗」と判定されるようになりました。
bash-5.0$ ./task.sh
hello!
bash-5.0$ echo $?
1
参考: shebang で指定する
shebang や bash の起動オプションで指定しても同じことができます。
task.sh
#!/bin/bash -e -o pipefail
# .. 後続の処理はそのまま ..
bash-5.0$ ./task.sh
hello!
bash-5.0$ echo $?
1
まとめ
- bash のパラメータを指定することで 外部コマンドの実行やパイプラインの途中で失敗した場合に即座にエラーコードを返す ことができる
set -e
set -o pipefail
CI などのマネージドサービスでシェルスクリプトを実行させる場合に、知っておくと便利な Tips です。