プロセスの終了ステータスを$?
で取得し、if文で判断するshellスクリプトは書いてはいけない。if文を使うということは、条件文としてtest
コマンドを使うことになり、ifとelif(の中のtest
コマンド)が実行されるたびに、終了ステータス$?
が変わってしまう。
以下にサンプルコードを書く。
$ cat test.sh
#!/bin/sh
return_val() {
echo '"$?" is 2'
return 2
}
return_val
if [ $? = 3 ]; then
echo '"[ $? = 3 ]" is matched. $? is' $?
elif [ $? = 2 ]; then
echo '"[ $? = 2 ]" is matched. $? is' $?
else
echo '"else" is matched. $? is' $?
fi
実行してみると、次のようにif文が正しくマッチしない。
$ sh test.sh
"$?" is 2
"else" is matched. $? is 1
return_val
を実行した結果、$?
は2になる。しかし、初めのif [ $? = 3 ]
が条件にマッチしないため、test
コマンド([
コマンド)の終了ステータスである1が$?
を上書きしてしまう。その結果、つぎのelif [ $? = 2 ]
が本来マッチしてほしいはずなのにマッチしなくなってしまう。
運よくif文の初めの条件文でマッチした場合は正しく判定できるが、if文内のコマンド実行部分ではすでに$?
が0で上書きされてしまっているため、$?
を使用できない。
対策は簡単で、$?
を変数に格納してあげればいい。
$ cat test2.sh
#!/bin/sh
return_val() {
echo '"$?" is 2'
return 2
}
return_val
ret=$?
if [ $ret = 3 ]; then
echo '"[ $ret = 3 ]" is matched. $? is' $?
elif [ $ret = 2 ]; then
echo '"[ $ret = 2 ]" is matched. $? is' $?
else
echo '"else" is matched. $? is' $?
fi
実行してみると、正しいif文にマッチする。ただし$?はif文内では先述の通りtest
コマンドの結果で上書きされているため使用できない。
$ sh test.sh
"$?" is 2
"[ $ret = 2 ]" is matched. $? is 0
if test $? = 3
と書いていれば、$?
がtest
コマンドで上書きされることに気づきやすい。しかし、今回のように[
コマンドで書いてしまうと、実際にはtest
コマンドで書いたことと同じ扱いなのに、if文の構文のように見えてしまう。コマンドを実行したという意識が希薄になって$?
が上書きされることにも気づきにくくなるので、注意が必要。