ERRおよびEXITトラップとともにset -e
(errexit
)、set -u
(nounset
)を使用すると、奇妙な動作が観察されます。それらは関連しているように見えるので、それらを1つの質問に入れるのは妥当なようです。
set -u
はERRトラップをトリガーしませんコード:
#!/bin/bash
trap 'echo "ERR (rc: $?)"' ERR
set -u
echo ${UNSET_VAR}
set -e
は結果を変更しませんset -eu
を使用すると、EXITトラップの終了コードが1ではなく0になるコード:
#!/bin/bash
trap 'echo "EXIT (rc: $?)"' EXIT
set -eu
echo ${UNSET_VAR}
set +e
を使用する場合、RC == 1です。他のコマンドがエラーをスローすると、EXITトラップは適切なRCを返します。誰かがこれらの行動のいずれかを説明できますか?
これらのトピックの検索はあまり成功しませんでしたが、Bashの設定とトラップに関する投稿の数を考えると、かなり意外です。 1つのフォーラムスレッド がありますが、結論はかなり満足できません。
_man bash
_から:
set -u
_ "@"
_および_"*"
_以外の未設定の変数およびパラメーターは、パラメーター展開を実行するときにエラーとして扱います。設定されていない変数またはパラメーターに対して展開が試行された場合、シェルはエラーメッセージを出力し、そうでない場合は_-i
_ nteractiveの場合、ゼロ以外のステータスで終了します。POSIXは、拡張エラーが発生した場合、非インタラクティブシェルは拡張がいずれかに関連付けられている場合に終了すると述べていますシェルの特別な組み込み(これはbash
の区別ですが、とにかく定期的に無視されるため、おそらく無関係です)またはその他のユーティリティ。
"${x!y}"
_、_!
_は有効な演算子ではないため);実装mayこれらは、拡張中ではなくトークン化中に検出できる場合、構文エラーとして扱います。また、_man bash
_から:
trap ... ERR
_ while
またはuntil
キーワードの直後のコマンドリストの一部である場合は実行されません...if
ステートメントのテストの一部...&&
_または_||
_...に続くコマンドを除く、_&&
_または_||
_リストで実行されるコマンドの一部...!
_を使用して反転されている場合。-e
_オプションが従うのと同じ条件です。上記の[〜#〜] err [〜#〜]トラップは、一部のotherコマンドの戻り値の評価に関するものであることに注意してください。ただし、展開エラーが発生した場合、何も返すコマンドは実行されません。あなたの例では、echo
決して起こらない-シェルがその引数を評価して展開している間、_-u
_ nset変数に遭遇するため、明示的なシェルオプションで指定されているため、現在のスクリプトシェルをすぐに終了します。
そして、[〜#〜] exit [〜#〜]トラップがある場合は実行され、シェルは診断メッセージと0以外の終了ステータスで終了します-正確にそれがあるべきように。
rcについては、0ことですが、これはバージョン固有のバグであると考えられます。おそらく、 [〜#〜] exit [〜#〜]が同時に発生し、一方が他方の終了コードを取得しています(発生してはなりません)。そしてとにかく、bash
によってインストールされた最新のpacman
バイナリを使用します。
_bash <<\IN
printf "Shell options:\t$-\n"
trap 'echo "EXIT (rc: $?)"' EXIT
set -eu
echo ${UNSET_VAR}
IN
_
シェルの条件がスクリプトシェルの条件であることを確認できるように、最初の行を追加しました-インタラクティブではありません。出力は次のとおりです。
_Shell options: hB
bash: line 4: UNSET_VAR: unbound variable
EXIT (rc: 1)
_
最近の変更履歴 からの関連するメモをいくつか示します。
$?
_を正しく設定しない原因となったバグを修正しました。for
コマンドのexpansion errorsによって生成されたエラーメッセージに誤った行番号が含まれるバグを修正しました。trap
pableになりません。trap
ハンドラーの実行中にシグナルの受信をブロックせず、mosttrap
ハンドラーを再帰的に実行できるようにします(実行中にtrap
ハンドラーを実行します) trap
ハンドラが実行中です)。最も関連性が高いのは、最後か最初のどちらか、またはおそらく2つの組み合わせです。 trap
handlerは、その性質上asynchronousです。これは、そのジョブ全体が待機して処理するためです非同期信号。そして、_-eu
_と_$UNSET_VAR
_で同時に2つをトリガーします。
更新する必要があるかもしれませんが、気に入った場合は、別のシェルで完全に更新します。
(私はbash 4.2.53を使用しています)。パート1の場合、bashのマニュアルページには、「エラーメッセージが標準エラーに書き込まれ、非インタラクティブシェルが終了する」とだけ書かれています。 ERRトラップが呼び出されるとは言われていませんが、呼び出された場合に役立つと思います。
実用的にするために、未定義の変数をより明確に処理することが本当に必要な場合、可能な解決策は、ほとんどのコードを関数内に配置し、その関数をサブシェルで実行して、戻りコードとstderr出力を回復することです。 「cmd()」が関数である例を次に示します。
#!/bin/bash
trap 'rc=$?; echo "ERR at line ${LINENO} (rc: $rc)"; exit $rc' ERR
trap 'rc=$?; echo "EXIT (rc: $rc)"; exit $rc' EXIT
set -u
set -E # export trap to functions
cmd(){
echo "args=$*"
echo ${UNSET_VAR}
echo hello
}
oops(){
rc=$?
echo "$@"
return $rc # provoke ERR trap
}
exec 3>&1 # copy stdin to use in $()
if output=$(cmd "$@" 2>&1 >&3) # collect stderr, not stdout
then echo ok
else oops "fail: $output"
fi
私のbashでは
./script my stuff; echo "exit was $?"
args=my stuff
fail: ./script: line 9: UNSET_VAR: unbound variable
ERR at line 15 (rc: 1)
EXIT (rc: 1)
exit was 1