nikkie-ftnextの日記

イベントレポートや読書メモを発信

エラーが送出されたらそれをハンドルしてから終了するシェルスクリプトの書き方を理解しました(set -e, trap, exit)

はじめに

やらなくても良いことならやらない1、nikkieです。

今回は直近で知った、シェルスクリプト関連のアウトプットです。

目次

まとめ(バックアップしておく気付き)

  • exitはどこで実行されても(関数の中でも)、その行でシェルスクリプトを終了
  • set -etrapで、コマンドでエラーが送出されたとき、エラーハンドラを実行してからスクリプトを終了できる

今回のサンプルシェルスクリプト(開始状態)

#!/usr/bin/env bash

echo "かがみの孤城、みんな見て!"

echo "こころちゃんかわいい"

スクリプト名はexample.sh
実行権限を付けてから実行します。

$ ./example.sh
かがみの孤城、みんな見て!
こころちゃんかわいい

exit

シェルスクリプトにexitを書くと、そこでスクリプトを終了します。
以降のコマンドは実行されません。

#!/usr/bin/env bash

echo "かがみの孤城、みんな見て!"

exit

echo "こころちゃんかわいい"

exitの後の終了コードの指定を省略すると0(正常終了)です2

$ ./example.sh
かがみの孤城、みんな見て!
$ echo $?
0

異常終了を表すステータスコードを渡す例です:

#!/usr/bin/env bash

echo "かがみの孤城、みんな見て!"

- exit
+ exit 1

echo "こころちゃんかわいい"
$ ./example.sh
かがみの孤城、みんな見て!
$ echo $?
1

関数内のexitもそこでスクリプトを終了

関数を導入します。

#!/usr/bin/env bash

function awesome_function() {
    echo "Start function with $1"
    echo "End function"
}

awesome_function 42

exit 1

echo "こころちゃんかわいい"

awesome_function 42と関数を呼び出しました。
その後のexit 1スクリプトを終了します。

$ ./example.sh
Start function with 42
End function

では関数内でexitしたらどうなるでしょうか?

#!/usr/bin/env bash

function awesome_function() {
    echo "Start function with $1"
+    exit $1
    echo "End function"
}

awesome_function 42

exit 1

echo "こころちゃんかわいい"
$ ./example.sh
Start function with 42
$ echo $?
42

関数内のexitによりシェルスクリプト自体が終了しました!
exitを含む関数は、シェルスクリプトを制御することになるわけですね。

小まとめ:シェルスクリプトexit

マスタリングLinuxシェルスクリプト 第2版』1.6.1より

exitはシェルの組み込みコマンドであり、スクリプトを終了するために使われます。
終了ステータスを、整数の引数として指定します。

set -e

シェルスクリプトは途中のコマンドでエラーが送出されても、そのまま動き続けます。

#!/usr/bin/env bash

echo "かがみの孤城、みんな見て!"

# 存在しないファイルのls(故意にエラー送出)
ls spam

echo "こころちゃんかわいい"
$ ./example.sh
かがみの孤城、みんな見て!
ls: spam: No such file or directory
こころちゃんかわいい
$ echo $?
0

コマンドでエラーが送出された際、直ちにスクリプトを止める指定がset -eです。

#!/usr/bin/env bash
+ set -e

echo "かがみの孤城、みんな見て!"

# 存在しないファイルのls(故意にエラー送出)
ls spam

echo "こころちゃんかわいい"
$ ./example.sh
かがみの孤城、みんな見て!
ls: spam: No such file or directory
$ echo $?
1

「こころちゃんかわいい」が出力されていませんね3
スクリプトの終了コードは、最後に実行されたlsコマンドの終了コード1(No such file or directory)となっています。

小まとめ:シェルスクリプトset -e

『マスタリングLinuxシェルスクリプト 第2版』A.1.7.2より4

-eオプションを付けてsetコマンドを実行するとそれ以降にスクリプト中のコマンドでエラーが発生した場合(コマンドの終了ステータスが0でない場合)にスクリプトの実行を終了します。

trap

trapでエラー送出時に呼び出されるハンドラを指定できることを知りました。

#!/usr/bin/env bash
set -e

function error_handler() {
    echo "エラーハンドラです"
}

trap error_handler ERR

echo "かがみの孤城、みんな見て!"

# 存在しないファイルのls(故意にエラー送出)
ls spam

echo "こころちゃんかわいい"
$ ./example.sh
かがみの孤城、みんな見て!
ls: spam: No such file or directory
エラーハンドラです
$ echo $?
1

lsコマンド実行でエラーが送出されると、エラーハンドラを実行してから終了という動きをします。

  • trapで指定したエラーハンドラを実行
  • エラーが送出されているので、set -e指定によりスクリプトを終了

小まとめ:シェルスクリプトtrap

  • スクリプトの中のコマンドでエラーが送出されたときのエラーハンドラを登録できる
  • エラーハンドラはset -eスクリプトが終了する前に実行される

『マスタリングLinuxシェルスクリプト 第2版』A.1.8より

trapコマンドを使用する際は、最初の引数に実行したいコマンドを指定し、2番目以降の引数にコマンドを実行させたいシグナル名またはシグナル番号を指定します。

ERRとは特別なシグナル名の1つ

ERR(コマンド実行時にエラーが発生した場合)

trapコマンドの入り口となったのはこちらのエントリです:
trap コマンドを使ったシェルスクリプトのエラーハンドリング - CUBE SUGAR CONTAINER

これらを調べた背景

この3つについて調べたのは、エラーハンドラでexitしているスクリプトを読み解きたかったからです。

#!/usr/bin/env bash
set -e

function error_handler() {
    error_status=$?
    echo "エラーハンドラです"
    exit $error_status
    echo "エラーハンドラ終了"
}

trap error_handler ERR

echo "かがみの孤城、みんな見て!"

# 存在しないファイルのls(故意にエラー送出)
ls spam

echo "こころちゃんかわいい"
$ ./example.sh
かがみの孤城、みんな見て!
ls: spam: No such file or directory
エラーハンドラです
$ echo $?
1

exitset -etrapについて理解していった結果、エラーハンドラでのexitは不要と考えています。
その理由は、エラーハンドラでexitしなくてもset -eで止まるからです。

#!/usr/bin/env bash
set -e

function error_handler() {
    error_status=$?
    echo "エラーハンドラです"
-     exit $error_status
    echo "エラーハンドラ終了"
}

trap error_handler ERR

echo "かがみの孤城、みんな見て!"

# 存在しないファイルのls(故意にエラー送出)
ls spam

echo "こころちゃんかわいい"
$ ./example.sh
かがみの孤城、みんな見て!
ls: spam: No such file or directory
エラーハンドラです
エラーハンドラ終了
$ echo $?
1

シェルスクリプトの知見が豊富ではない(勉強中)ので間違っている可能性もありますが、エラーハンドラがexitする(シェルスクリプトを制御する)のは、責務が大きすぎるように感じます。
なので、set -eがあるならば、trapで設定するエラーハンドラでは(やらなくてもいい)exitは書かないというのが現時点の私の考えです。

終わりに

set -etrapがあれば、エラーハンドラ内でexitは不要!
よく分からないコマンドが複数ありましたが、1つ1つ理解していったことで、簡潔な実装ができることに気付けました。
急がば回れ」とはこのことですね。

そして、『マスタリングLinuxシェルスクリプト 第2版』、これはいい本ですね!5
今まで必要な箇所をWebで調べてシェルスクリプトを書いてきましたが、体系的に整理されていて、理解のムラがならされそうです。

(2023/05/02 追記)trapと一時ファイルを教えていただく

arterminalさん、ありがとうございます!


  1. 氷菓』の折木奉太郎のモットーです。
  2. $?は直前のコマンドの終了コードを確認できるという理解です
  3. この例にした自分が悪いのですが、「こころちゃんかわいい」が出力できなくて残念です
  4. A.1.7.3によると、set -Eとすることで、関数内のエラーに対してtrapコマンドで指定したハンドラが有効になるようです
  5. こちらのエントリでよさそうと思いました:『マスタリングLinuxシェルスクリプト 第2版』、こういう1冊手元に有るとずっと使える本はちゃんと買っておきたいですね - Magnolia Tech