シェルスクリプトで set -e
(errexit) しておくと、スクリプト中に実行したコマンドの終了ステータスが「非0」だった場合、つまりコマンドが失敗したときにそこでスクリプトを中断してくれるようになります。(終了ステータスとは「Exit code」「Return status」などと呼ばれるものです。)
都度エラー判定を書かなくてもよくなるので便利なオプションですが、個別にエラーハンドリングをしたくなった時にも意図せずスクリプトが中断されてしまい困ったことになります。
例えば、以下の例は false
コマンドの実行で失敗扱いとなり、そこでスクリプトが中断されるため「finish」が出力されることはありません。
set -e # false コマンドは必ず終了ステータスに 1 (失敗) を返すコマンド # エラーが起きたとみなされスクリプトは中断される false # このコマンドは実行されない echo "finish"
-e オプションで困ってしまうケースの例
例えば、grep
コマンドで検索結果が見つからなかった場合にエラーメッセージを出力したいケースがあったとしましょう。
grep
は検索結果が何か見つかれば 0 (成功)、見つからなければ 1 (失敗) の終了ステータスを返す仕様ですが、以下の例では grep
で見つからなかった場合、そこでスクリプトが中断されてしまうので、以降の if 文が実行されることはありません。
set -e grep "apple" hoge.txt >/dev/null ret=$? if [[ $ret != 0 ]]; then # ここに入ってくることはない! echo "hoge.txt には Apple を含む行が見つかりませんでした。" 1>&2 exit 1 fi
これについて対処法を4つメモしておきます。
対処法1: コロンコマンドを利用する (:コマンド)
コロンコマンド :
は一つの built-in command で、何もせず常に 0 (成功) の終了ステータスを返す虚無的なコマンドです。
以下の例はスクリプトが中断されることはなく「Exit code: 1」が出力されます。
set -e grep "unmatch pattern" hoge.txt && : echo "Exit code: $?"
これは -e オプションにおける特殊な振る舞いを利用した方法です。
式の評価の流れ:
grep
の終了ステータスが 0 (成功) だった場合、&&
演算子により右側も評価され:
を実行。一連のコマンドの終了ステータスは 0 (成功)。grep
の終了ステータスが 1 (失敗) だった場合、&&
演算子によりそこで評価を終了。なので一連のコマンドの終了ステータスは 1 (失敗)。
更に set -e
オプションには &&
や ||
で連結されたコマンドリストの最後にある(一番右側の)コマンドが失敗だった場合のみスクリプトを中断するという特性があるため、そのままスクリプトを続行できます。$?
にもちゃんと grep
の終了ステータスが入っています。
そのためちょっと直感的ではないですが、以下のコマンドの出力は route1のみ となります。
set -e false && false echo "route1" false echo "route2"
対処法2: true コマンドを利用する
以下の例は「Exit code: 1」が出力されます。
set -e grep "unmatch pattern" hoge.txt && true echo "Exit code: $?"
これも対処法1と同じ理由です。true
コマンドも :
と同じく何もせず常に 0 (成功) を返すコマンドです。
実質的に対処法1と同じ方法と言えますが、こちらの方が他の人に意図は伝わりやすいかもしれません。
対処法3: 終了ステータスを取得せず if 文でそのまま判定する
set -e # grep は検索結果が見つからなかった時に 1 (失敗) を返すコマンド # --quiet は標準出力に何も書き出さないオプション if ! cat hoge.txt | grep --quiet "Apple"; then echo "hoge.txt には Apple を含む行が見つかりませんでした。" 1>&2 exit 1 fi
これは最も自然な解決方法だと思います。if 文の評価式として実行したコマンドに関しては失敗しても中断されないという特性があるため、スクリプトを続行できます。
対処法4: 一時的に set +e して -e オプションを解除する
以下の例は「Exit code: 1」が出力されます。
set +e grep "unmatch pattern" hoge.txt echo "Exit code: $?" set -e
この方法が楽そうならこれで。最後に set -e
で元に戻すのを忘れずに。
感想
対処法3のケースなど普段何気なく実現できていたことも、ちょっとした挙動の特性により実現出来ていたことが理解できました(^^)
個人的にはコロンコマンドを使用した方法がシンプルで好きですが、時と場合で使い分けたいと思います。
参考資料
- 4.3.1 The Set Builtin – 英語ですが
set -e
オプションを指定した際の挙動について説明されています。 - Bash – set -e しているときにコマンドの終了ステータスを得る – Qiita – 記事を書くにあたり参考にさせていただきました。
編集履歴
- 2018/11/22: 全体的に例や内容をブラッシュアップしました。
大変参考になりました。ありがとうございます。
下記のコマンドの存在チェックで困っていたので、対処法2を使います。
type command >/dev/null 2>&1