LoginSignup
17
13

More than 3 years have passed since last update.

サブシェルやパイプラインの途中で失敗した場合に直ちにエラーコードを返すようにする

Last updated at Posted at 2019-08-18

はじめに

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 です。

参考資料

  1. Bash Reference Manual - 4.3.1 The Set Builtin
  2. stack overflow - How to exit if a command failed?
17
13
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
17
13