シェルスクリプトでset -eしているときに処理を中断せずエラーを扱う方法

シェルスクリプトで set -e しておくと、実行したコマンドの戻り値が非0で失敗したとき、そこでスクリプトの処理を中断してくれます。

エラー判定の処理を書かなくてもよくなるので、書き捨てるスクリプトを書くときなどとても便利ですが、エラーハンドリングして何かをしたい場合は意図せず処理が中断されてしまい困ったことになります。

例えば以下の場合、false コマンドの戻り値は1(失敗)なのでそこで処理は中断され”finish”の出力は行われません。

#!/bin/bash -e
false
echo "finish"

-eオプションで困ってしまうケースの例

例えば grep コマンドの戻り値は、検索結果が見つかれば0(成功)、そうじゃなければ1(失敗)となりますが、検索結果が見つからなかったときも続きの処理を行いたい場合は困ったことになります。

以下の例は、grep コマンドの検索結果が見つからなかった場合にそこで処理が中断されてしまい、続きの処理を行えません。

#!/bin/bash -e
grep "unmatch pattern" hoge.txt
echo "return value: ${?}"

この対処法を4つメモしておきます。

対処法1: コロンコマンドを使用する (:コマンド)

#!/bin/bash -e
grep "unmatch pattern" hoge.txt && :
echo "return value: ${?}"

→ return value: 1

解説

これは-eオプションにおける特殊な特性を利用した方法です。
コロンの部分はひとつのコマンドで、何もせず0(成功)の戻り値を返すコマンドです。

式の評価の流れ:

  • grep コマンドの戻り値が0(成功)だった場合、&& 演算子により右側も評価されコロンコマンドを実行。一連のコマンドの戻り値は0(成功)。←ややこしい^^;
  • grep コマンドの戻り値が1(失敗)だった場合、&& 演算子により処理はそこで中断。なので一連のコマンドの戻り値は1(失敗)。

set -eオプションは && や || で結合された式リストの最後のコマンドが1(失敗)だった場合のみスクリプトを中断するという特性があるためそのままスクリプトは続行されます。$?にもちゃんと grep の戻り値が入っています。

なのでちょっと直感的ではないですが、上記の特性により以下のコマンドの出力は“route1″のみとなります。

#!/bin/bash -e
false && false
echo "route1"

false
echo "route2"

対処法2: trueコマンドを使用する

grep "unmatch pattern" hoge.txt && true
echo "return value: ${?}"

→ return value: 1

解説

対処法1と同じ理由です。true コマンドもなにもせず0(成功)を返すコマンドです。

対処法3: 戻り値を取得せずに処理を行う

#!/bin/bash -e
if "pattern" hoge.txt ; then
  # "Matched case, return value is 0."
  echo "return value: ${?}"
else
  # "Unmatched case, return value is 1."
  echo "return value: ${?}"
fi

解説

この方法は一番シンプルに解決できる方法かもしれません。
これも-eオプションにおける挙動の特性を利用しており、if の評価式の戻り値が1(失敗)の場合でも処理は中断されないという特性があるため処理を続行できます。

対処法4: 一時的に set +e して -e オプションを解除する

#!/bin/bash -e
set +e
grep "unmatch pattern" hoge.txt
echo "return value: ${?}"
set -e

→ return value: 1

解説

個人的に少しダサさを感じてしまいますが、この方法が楽そうならこれで。最後にset -eで元に戻すのを忘れずに。

まとめ

対処法3のケースなど普段何気なく実現できていたことも、ちょっとした挙動の特性により実現出来ていたことが理解できました^^

この記事は bash を前提としていますが、他のシェルの場合の挙動はどうなるか分かりません。

個人的にはコロンコマンドを使用した方法がシンプルで好きですが、時と場合で使い分けで。

参考資料