プロセスの終了ステータスを$?で取得し、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文の構文のように見えてしまう。コマンドを実行したという意識が希薄になって$?が上書きされることにも気づきにくくなるので、注意が必要。