読者です 読者をやめる 読者になる 読者になる

異常時にちゃんと止まるシェルスクリプト

自動化は大切ですが、自動化するときは異常時にちゃんと止まるようにしなければなりません。トヨタ自動車でいうところのニンベンのついた「自働化」である。なぜ自働化が必要かというと、エラー時に止まってくれない機械は稼働中に人が張り付いて見守っていなければならないからです。また、エラーが起こったのちに機械が動き続けるならば、異常なアウトプットが出力され続けることになり、異常なアウトプットを処理しなければならないというムダが発生します。自動化のためのスクリプトを書くときは、エラー時には止まるように作らなければなりません。

しかしながら、シェルスクリプトは恐ろしいことにエラーが起きても既定ではそのまま処理を続けていきます。異常時にちゃんと止まる自働化スクリプトとするためには以下をスクリプトの先頭に書くと良いでしょう。

set -eu -o pipefail
trap 'echo "ERROR: line no = $LINENO, exit status = $?" >&2; exit 1' ERR
set -e
set -e
echo 1
false
echo 2

上記のシェルスクリプトを実行すると、以下のように false のところ、つまり 0 以外で exit したところで止まります。

$ bash sete.sh
1
$ echo $?
1
trap

しかし、上記ではどこで止まったのかが分からないので、どこで止まったのかを trap を用いて表示するようにします。

set -e
trap 'echo "ERROR: line no = $LINENO, exit status = $?" >&2; exit 1' ERR
echo 1
false
echo 2

trap に第1引数で指定されたコマンドがエラー発生に実行されます。 LINENO 変数には現在の行番号が格納されています。実行と以下のようにエラー発生時の行番号と exit status が標準エラー出力に表示されて止まります。

$ bash traperr.sh 
1
ERROR: line no = 4, exit status = 1
$ echo $?
1
pipefail

set -e だけだとコマンドをパイプでつないだ場合に上手く止まらない場合があります。

set -e
trap 'echo "ERROR: line no = $LINENO, exit status = $?" >&2; exit 1' ERR
echo 1
false | true
echo 2

を実行すると、 false コマンドが失敗しているはずなのに、止まりません。

$ bash pipefail1.sh 
1
2
$ echo $?
0

set -o pipefail をつけるとパイプの途中のコマンドが失敗しても、止まります。

$ cat pipefail2.sh 
set -e -o pipefail
trap 'echo "ERROR: line no = $LINENO, exit status = $?" >&2; exit 1' ERR
echo 1
false | true
echo 2
$ bash pipefail2.sh 
1
ERROR: line no = 4, exit status = 1
$ echo $?
1
set -u

set -u を指定すると宣言してない変数が使用されるとエラーになります。

$ cat setu.sh 
set -u
echo 1
echo $AAA
echo 2
$ bash setu.sh 
1
setu.sh: line 3: AAA: unbound variable

自働化は運用時の話なので、タイポなんてのは運用前になくなっているはずなので、運用時にはなくてもいいですが、間違っているのに動いちゃっているという状態にならないためにつけておくべきでしょう。