madokaのブログ

勉強したことのoutput先として使ってます。内容はpythonがらみが多いかもです。

shellでtry-catchをする

例えば以下のようなコードで、移動先のディレクトリがなかったらhelloはechoされないとわたしは思っていたのですが、実際のところはhelloまで出でしまいます。

#!/bin/sh

cd hoge
echo hello

↓出力

sample.sh: line 3: cd: hoge: No such file or directory
hello

そもそもshellはエラーが出たことを認識できているのでしょうか?

コマンドの成否を確認する

終了ステータス | UNIX & Linux コマンド・シェルスクリプト リファレンス

コマンド終了時には「終了ステータス (exit-status)」と呼ばれるコマンドの成否を表す数値が特殊変数 $? に自動で設定される。

終了ステータスとして設定される値は一般的には

  • 成功:0
  • 失敗:1 (コマンドやエラーの種類によっては 0 以外)

が設定されるそうです。

ということなのでエラーを認識できているのか確認は以下で行います。

#!/bin/sh

cd hoge
echo $?
echo hello
echo $?

↓出力

sample.sh: line 3: cd: hoge: No such file or directory
1
hello
0

どうやらエラーであることは認識できているようです。 ならばtry-catchしたいということでshellでtry-catchする方法を調べてみました。

shellでtry-catch

shell try-catchで検索したらこちらのサイトが見つかりました。

シェルスクリプトでtry-catch-finally - Qiita

こちらのサイトによるとtry-catchするなら以下のように書けば良いことがわかりました。

#!/bin/sh

set -eu
function catch {
  echo Catch
}
function finally {
  echo Finally
}
trap catch ERR
trap finally EXIT

cd hoge
echo hello

これを出力すると以下になりました。

sample.sh: line 13: cd: hoge: No such file or directory
Catch
Finally

先ほどまで出力されていたhelloの文字が出力されなくなり、Catch、Finallyの文字が出力されたことから、cd hogeでエラーが発生したタイミングで以降の処理がスキップされ、trapで設定したcatch、finallyを通過して終了したことがわかります。

同様に成功するパターンも確認しておきましょう。

#!/bin/sh

set -eu
function catch {
  echo Catch
}
function finally {
  echo Finally
}
trap catch ERR
trap finally EXIT

cd .
echo hello

この出力は以下のようになりました。

hello
Finally

以上より、trapおよびset -euによってどういうわけかtry-catchができるようになりました。

set -euとは

shellのコマンドのsetとそのオプションであるeuからなっているように見えるのでshellのsetについて調べてみました。見つかったのがこちらです。

【 set 】コマンド――シェルの設定を確認、変更する:Linux基本コマンドTips(205) - @IT

まずはsetというコマンドはshellの設定の確認、変更を行うコマンドとなっています。 そして、euは以下のように説明されています。

option option 名 説明
e errexit パイプやサブシェルで実行したコマンドが1つでもエラーになったら直ちにシェルを終了する
u nounset パラメーター展開中に、設定していない変数があったらエラーとする(特殊パラメーターである「@」と「*」は除く)

今回エラー発生タイミングで以降の処理をスキップをするようになったのは、set -eのおかげであるということですね。

ちなみにset -uの恩恵を受けるようなコードは以下になります。

#!/bin/sh

set -u

$hoge

echo hello

↓出力

sample.sh: line 5: hoge: unbound variable

hogeという設定していない変数を利用しようとした結果エラーになりました。この設定によるエラーはeのオプションなしでも以降の処理はスキップして終了するのですね。念の為オプションなしの方も確認しておきましょう。

#!/bin/sh

$hoge

echo hello

↓出力

hello

エラーが吐かれることなく最後まで実行していました。

trapとは

trap - シグナルをトラップする

trap [ action condition ... ]という構文からなっています。

trap ユーティリティーは、 condition が発生したときに qsh によって実行される action を設定します。 qsh は、trap を実行しているときに action を展開し、condition が発生したときに再度展開します。 また、trapにはl,pというオプションがあり、それぞれ以下のようにことができます。

option 説明
l すべてのシグナル名とその対応する番号のリストを表示します。
p 各トラップを再入力可能な形式で表示します。

conditionにはシグナルの名または番号を指定します。有効なシグナルのリストはtrap -lで参照することができるようです。 また、デフォルトで存在するのは以下です。

condition 説明
ERR エラー時に(コマンドの返り値が0以外)の時に実行
EXIT (0) 終了時に実行
DEBUG 各種コマンドの後で実行

まとめ

shellでも他のプログラミング言語でできることはできるんだなあと思いました。