CUBE SUGAR CONTAINER

技術系のこと書きます。

trap コマンドを使ったシェルスクリプトのエラーハンドリング

今回は、シェルの組み込みコマンドの trap を使ったシェルスクリプトのエラーハンドリングについて。 シェルの組み込みコマンド trap は、特定のシグナルやコマンドの返り値が非ゼロとなったときに実行する処理を指定できる。

trap コマンドは、次のようにして使う。 以下の <arg> が実行する処理で、<sigspec> が反応させたいシグナルや状況となる。

$ trap <arg> <sigspec>

使った環境は次の通り。

$ sw_vers
ProductName:    Mac OS X
ProductVersion: 10.14.6
BuildVersion:   18G103
$ bash -version
GNU bash, version 3.2.57(1)-release (x86_64-apple-darwin18)
Copyright (C) 2007 Free Software Foundation, Inc.

コマンドが非ゼロの返り値を返したときのハンドリング

以下のサンプルコードでは、コマンドの返り値が非ゼロになったときに標準エラー出力に "ERROR" という文字列を表示する。 このサンプルコードでは、最後に実行結果として非ゼロを返す false コマンドを実行しているため、必ずエラーハンドラが実行される。 trap コマンドの <arg> には関数 error_handler() を指定してあって、<sigspec> には非ゼロが返ったときに反応する ERR を指定している。

#!/usr/bin/env bash

# 実行した処理を標準出力に記録する
set -x

# エラーになったときに実行したい関数
function error_handler() {
  # 何か起きたことを標準エラー出力に書く
  echo "ERROR" >&2
  # スクリプトを終了する
  exit 1
}

# コマンドの返り値が非ゼロのときハンドラを実行するように指定する
trap error_handler ERR

# 例として非ゼロを返すコマンドを実行する
false

上記を適当な名前で保存して実行してみよう。

$ bash errhandle.sh 
+ trap error_handler ERR
+ false
++ error_handler
++ echo ERROR
ERROR
++ exit 1

ちゃんとエラーハンドラが発火して "ERROR" という文字列が表示されていることがわかる。

ちなみに、trap コマンドで指定されたハンドラは、スクリプトの中で set -E されていたとしても発火する。 set -E は、コマンドの返り値が非ゼロになった時点でスクリプトの実行を止めるという指定になる。

#!/usr/bin/env bash

# コマンドの返り値が非ゼロになった時点で止める
set -E

# 実行した処理を標準出力に記録する
set -x

# エラーになったときに実行したい関数
function error_handler() {
  # 何か起きたことを標準エラー出力に書く
  echo "ERROR" >&2
  # スクリプトを終了する
  exit 1
}

# コマンドの返り値が非ゼロのときハンドラを実行するように指定する
trap error_handler ERR

# 例として非ゼロを返すコマンドを実行する
false

実行結果は先ほどと変わらない。

$ bash errhandle.sh
+ trap error_handler ERR
+ false
++ error_handler
++ echo ERROR
ERROR
++ exit 1

プロセスが特定のシグナルを受信したときのハンドリング

同様に、プロセスが特定のシグナルを受信したときのハンドリングについても確認しておく。

以下のサンプルコードでは SIGINT シグナルを受信したときにハンドラが発火するように trap コマンドで指定している。 ハンドラでは "SIGINT" という文字列を標準エラー出力に表示する。 スクリプトは 2 秒のスリープをはさみながら、無限ループで SIGINT を待ち受ける。

#!/usr/bin/env bash

# SIGINT を受け取ったら実行するハンドラ
function sigint_handler() {
  echo "SIGINT" >&2
  exit 0
}

# SIGINT を受け取ったときにハンドラを実行する
trap sigint_handler SIGINT

# 無限ループで SIGINT を待つ
while true;
do
  echo "press Ctrl+C to stop"
  sleep 2
done

上記も名前をつけて保存したら実行してみよう。 SIGINT はキーボードの Ctrl + C キーを使って送れる。

$ bash trapsigint.sh
press Ctrl+C to stop
press Ctrl+C to stop
press Ctrl+C to stop
^CSIGINT

どうやら、ちゃんとハンドラが発火しているようだ。

いじょう。