"set -eu"を使用した場合のEXITおよびERRトラップの正しい動作

"set -eu"を使用した場合のEXITおよびERRトラップの正しい動作

set -eerrexit)、set -u()、ERR、およびEXITトラップを使用するnounsetと、いくつかの奇妙な動作が観察されました。互いに関係があるようだから、一つの質問で結ぶのが妥当に見えます。

1) set -uERR トラップをトリガしません。

  • パスワード:

    #!/bin/bash
    trap 'echo "ERR (rc: $?)"' ERR
    set -u
    echo ${UNSET_VAR}
    
  • 予想:ERRトラップ呼び出し、RC!= 0
  • 実際:ERRトラップは次のとおりです。いいえ呼び出された、RC == 1
  • 注:set -e結果は変更されません。

2) set -euEXIT トラップで 1 の代わりに終了コード 0 を使用します。

  • パスワード:

    #!/bin/bash
    trap 'echo "EXIT (rc: $?)"' EXIT
    set -eu
    echo ${UNSET_VAR}
    
  • 予想:EXITトラップが呼び出されます。RC==1
  • 実際:EXITトラップが呼び出されます。RC==0
  • 注:使用時はset +eRC == 1です。他のコマンドでエラーが発生すると、EXITトラップは正しいRCを返します。
  • 編集する:このトピックに関する投稿があります興味深いコメントこれは、使用しているBashのバージョンに関連している可能性があることを示します。 Bash 4.3.11でこのコードスニペットをテストすると、RC = 1が提供されるため、よりうまく機能します。残念ながら、現在のすべてのホスト(3.2.51ベース)でBashをアップグレードすることは不可能であるため、他のソリューションを見つける必要があります。

誰でもこれらの行動を説明できますか?

これらのトピックを検索することはあまり成功しませんでしたが、Bashの設定とトラップの投稿数を考慮すると、かなり驚くべきものです。持つフォーラムトピックそれでも結論は満足できない。

答え1

からman bash

  • set -u
    • パラメータ拡張時に設定されていない変数と特殊パラメータ以外のパラメータはエラー"@"として扱われます"*"。設定されていない変数またはパラメータに対して拡張しようとすると、シェルはエラーメッセージを出力し、対話型でない-i場合はゼロ以外の状態で終了します。

POSIXでは、次のように言います。インフレエラー、非対話型シェル終了する必要がある拡張がシェル特殊組み込みに関連付けられている場合(これはとにかく見過ごされやすい区別bashなので、関係がないかもしれません。)それとは別のユーティリティもあります。

  • シェルエラーの結果:
    • 一つインフレエラーシェル拡張が定義されている場合。単語拡張指揮する(例:は有効な演算子ではない"${x!y}"ため)!;実装可能拡張中ではなくトークン化中に検出できる場合は、構文エラーと見なされます。
    • [A]n対話型シェルは終了せず、標準エラーに診断メッセージを書き込む必要があります。

また、以下からman bash

  • trap ... ERR
    • シグスペックが次の場合よく、注文するアルギニンパイプラインがあるたびに実行(簡単なコマンドで構成できます)、リスト、または複合コマンドは、次の条件が満たされた場合、ゼロ以外の終了状態を返します。
      • これよく失敗したコマンドが、whileまたはuntilキーワードの直後に続くコマンドのリストの一部である場合、トラップは実行されません。
      • ...ドアのテストの一部ですif...
      • &&||...最後または次のコマンドを除いて、リストから実行されるコマンドの一部...&&||
      • ...最後のコマンドを除くパイプラインのすべてのコマンド...
      • ...またはコマンドの戻り値を反転するために使用される場合!
    • これらの条件は以下に関連しています。エラーが発生したため終了 -eオプション。

上記を参考にしてくださいよく罠は特定人に対する評価だ。その他コマンドの戻り。しかし、いつインフレエラーこれが発生すると、何も返さないコマンドは実行されません。あなたの例ではecho 決して起こらない- シェルが引数を評価して拡張すると、-u明示的なシェルオプションで指定されたnset変数が見つかり、現在スクリプトされているシェルがすぐに終了するためです。

だから出口トラップ(存在する場合)が実行され、シェルは診断メッセージとゼロ以外の終了状態で終了します。

についてはコントロールセンター:0問題は、これが一種のバージョン固有のバグであることを願っています。おそらく2つのトリガーと関係があるでしょう。出口同時に発生し、1人の当事者が相手の終了コードを取得します。(このようなことは起こらないでください)。とにかく、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行番号が正しくありません。
  • 発生した問題を解決しました。知能を知らせるそして信号出口trap非同期サブシェルコマンドでは使用できません。
  • 2回目以降のイベントを引き起こす割り込み処理の問題を修正しました。知能を知らせる対話型シェルでは無視されます。
  • シェルは、これらのシグナルに対してハンドラを実行するときにシグナルの受信をブロックしなくなり、次のことをtrap許可します。最大 trapハンドラを再帰的に実行 (ハンドラの実行trap中にハンドラを実行します)trap

私の考えに最も関連性が高いのは、最後の項目、または最初の項目、または2つの組み合わせです。 ㅏtrapマネージャーその性格上非同期なぜならそれがすることは待って処理するだけだからです。非同期信号-eu両方を同時に使用してトリガーできます$UNSET_VAR

したがって、更新する必要があるかもしれませんが、自分が気に入ったら、まったく異なるシェルを使用して更新することもできます。

答え2

(私はbash 4.2.53を使用しています)。パート1の場合、bashのマニュアルページには「エラーメッセージが標準エラーに書き込まれ、非対話型シェルが終了します」とのみ表示されます。 ERRトラップが呼び出されるとは言いませんが、そうすれば役に立つことに同意します。

実用的に言えば、本当に望むものが未定義の変数をよりきれいに処理することであれば、可能な解決策の1つはほとんどのコードを関数に入れてからサブシェルで関数を実行し、戻りコードと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

私の乱交で私を得る

./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

答え3

nounsetで使用する方法を探し、これを発見した人のためにtrap ERR

以下はエラートラップをトリガしません。

set -o nounset

trap 'trapped error on line $LINENO' ERR

echo $foo

期待される出力

trapped error on line 5

実際の出力

./script: line 5: foo: unbound variable

このフラグを有効にしたい場合は、nounset1つの回避策は終了トラップで戻りコードを確認することです。

set -o nounset

trap '[[ $? != 0 ]] && echo caught error' EXIT

echo $foo

出力

./script: line 5: foo: unbound variable
caught error

関連情報