bashでset -eやtrap使ってtry-catch+throw処理をする方法

ググっているとset -eやtrapを使うことでbashでもtry-catch的なことができることがわかります。

suz-lab - blog: シェルスクリプトで例外処理(try-catch文)的なもの

エラー監視時(set -e)の汎用トラップコード(trap) - Qiita

私もこれらのブログから先人の知恵をいただきながら便利に使わせていただいてます。

この時点でかなり便利なのですが、エラーでは無いけど、任意のタイミングでthrowを上げたい場合は、外部ファイルや専用の関数にEXIT1で終わる処理を組み込み呼ぶ方法しか見つかりませんでした。

サクッとthrowを呼んで、あわよくば、メッセージやエラーが起きた行、エラーコードも取得して、エラーハンドリングに利用したいなと思っていたら、少しだけ良い方法が見つかったので書いておきます。

Javaで言えば単純にこれがしたいだけです。

throw new Exception("exception!!");

今回throwの代わりに使うのは以下のような処理です。MESSAGE変数にメッセージ文字列格納して、適当なエラーコードを投げます(以下の例の場合は10)

MESSAGE="exception!!"
$(exit 10)

わかりずらいかと思うので、試しに一つスクリプト例を作って説明します。

スクリプト

引数で渡されたディレクトリの中のアイテム数をチェックするスクリプトを作ってみます。

スクリプト例の要件

  • 引数のディレクトリにアイテムが存在すればアイテム数をメッセージ出力して、EXIT 0で終わります。
  • 引数のディレクトリが空ならばエラーメッセージを出力して、EXIT 2で終わります。
  • 引数がそもそもディレクトリで無ければエラーメッセージを出力して、EXIT 10で終了します。

スクリプト

このようなスクリプトを書いてみました。

#!/bin/bash
set -ue
MESSAGE=
onerror(){
  error_code=$1
  line_num=$2
  message=$3
  errmsg=
  errmsg+="----------------------------\n"
  errmsg+="Error Code : ${error_code}\n"
  errmsg+="Message : ${message}\n"
  errmsg+="Command Line : ${line_num}\n"
  errmsg+="----------------------------\n"
  echo -e ${errmsg}
  exit ${error_code}
  
}
trap 'onerror $? $LINENO "$MESSAGE"' ERR 

target_dir=$1
if [ -d "${target_dir}" ]; then
  file_num=`ls -AF ${target_dir} | grep -v / | wc -l`
  if [ ${file_num} -eq 0 ]; then
    MESSAGE="[${target_dir}] is empty."
    $(exit 2)
  else
    echo "[${target_dir}] has ${file_num} files."
  fi  
else
  MESSAGE="[{$target_dir]} is NOT directory."
  $(exit 10)
fi
echo "Completed Successfully"

チェック対象ディレクトリ構成

チェック対象のディレクトリ構成です。

$ tree /path_to_dir
/path_to_dir
├── empty
└── somefiles
    ├── file1
    ├── file2
    └── file3

実行結果

上で書いた要件の順番に実行していきます。

まずは正常系。アイテムが存在するディレクトリをチェックします。

$ /path_to_shell/checkdir.sh /path_to_dir/somefiles
[/pathtodir/somefiles] has 3 files.
Completed Successfully
$ echo $?
0

異常系1、空のディレクトリチェックです。エラーコードやメッセージ、エラーの発生場所が、エラーメッセージに表示できています。途中で処理が停止しているため、Completed Successfullyが表示されないことがわかります。

$ /path_to_shell/checkdir.sh /path_to_dir/empty
----------------------------
Error Code : 2
Message : [/path_to_dir/empty] is empty.
Command Line : 25
----------------------------
$ echo $?
2

異常系2、引数にディレクトリでなくファイルを指定してみます。エラーハンドリング情報を取得できています。

$ /path_to_shell/checkdir.sh /path_to_dummy/dummy
----------------------------
Error Code : 10
Message : [/path_to_dummy/dummy] is NOT directory.
Command Line : 31
----------------------------
$ echo $?
10

このようにtry-catchに加えてthrow的なこともできました。