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


27

set -eerrexit)、set -unounset)とともにERRおよびEXITトラップを使用すると、奇妙な動作が見られます。それらは関連しているように見えるので、それらを1つの質問に入れるのは合理的です。

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 +e場合、RC == 1です。他のコマンドがエラーをスローすると、EXITトラップは適切なRCを返します。
  • 編集:このトピックには、使用されているBashバージョンに関連している可能性があることを示唆する興味深いコメントを含むSO投稿があります。このスニペットをBash 4.3.11でテストすると、RC = 1になります。残念ながら、現時点ではすべてのホストでBashを(3.2.51から)アップグレードすることはできないため、他のソリューションを考え出す必要があります。

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

これらのトピックの検索はあまり成功しませんでした。Bashの設定とトラップに関する投稿の数を考えると、これはかなり驚くべきことです。ある1つのフォーラムのスレッドはいえ、しかし、結論は、むしろ不満足です。


3
4からbash、標準を破り、サブシェルにトラップを入れ始めたと思います。トラップは、どこから戻ってきたかと同じ環境で実行されることになっていますが、bashしばらくは実行されていません。
mikeserv

1
ちょっと待ってください-解決策や説明が必要ですか?そして、あなたが解決策を望むなら、正確に何に対する解決策ですか?何が起こりたいですか?set -eそしてset -u両方のために特別に設計されて殺すスクリプトシェルを。う彼らのアプリケーショントリガする可能性がある状況でそれらを使用して殺すスクリプトシェルを。それらを使用せ、代わりにコードシーケンスで適用するときにそれらの条件をテストすることを除いて、それを回避することはできません。したがって、基本的には、適切なシェルコードを記述するか、を使用できますset -eu
mikeserv

2
実際、私は両方を探しています。理由-uは、ERRトラップをトリガーしない理由に関する十分な情報を見つけることができなかったためです(エラーであるため、トラップをトリガーするべきではありません)、またはエラーコードは1ではなく0です。後者は、後のバージョンですでに修正されているバグのようですので、それだけです。しかし、シェル評価(パラメーター展開)のエラーとコマンドの実際のエラーが2つの異なるものであることに気付いていない場合、最初の部分を理解するのは非常に困難です。解決策については、あなたが提案したように、-eu必要なときに手動で回避して確認しようとしています。
dvdgsng

1
@dvdsng-良い。これが方法です-回答としてスクリプトを投稿し、自分に賞金を授与する必要があります。私はこれらのオプションが本当に嫌いです-安全な方法で例外処理を許可していません。
mikeserv

1
@dvdsng-ただし、これらのオプションのいずれかが役立つ場合は、サブシェルのコンテキストにあります。したがって、以前にそれらを使用していたものはすべて、次のようなサブシェルコンテキストにローカライズできると考えられます(set -u; : $UNSET_VAR)。この種のものも良いことがあります- 私のドリフトを取得する場合、あなたは&&時々多くのものをドロップすることができます:(set -e; mkdir dir; cd dir; touch dirfile)。それらが制御されたコンテキストであるというだけです-それらをグローバルオプションとして設定すると、制御を失い、制御されます。ただし、通常はより効率的なソリューションがあります。
mikeserv

回答:


15

からman bash

  • set -u
    • 治療解除変数および特殊なパラメータ以外のパラメータ"@""*"パラメータ展開を行う際にエラーとして。設定されていない変数またはパラメーターで展開が試行されると、シェルはエラーメッセージを出力-iし、非アクティブでない場合は、ゼロ以外のステータスで終了します。

POSIXは、拡張エラーが発生した場合、拡張がシェルの特別なビルトイン(区別は通常無視されるため、おそらく無関係である)または他のユーティリティに関連付けられている場合、非対話型シェルは終了することを述べています。bash

  • シェルエラーの結果
    • 拡張エラーがで定義されたシェル拡張ときに発生するものである単語展開が行われている(例えば、"${x!y}"ので、!有効な演算子はありません)。実装では、展開中ではなくトークン化中に検出できる場合、これらを構文エラーとして扱う場合があります。
    • [A] n対話型シェルは、終了せずに診断メッセージを標準エラーに書き込みます。

からもman bash

  • trap ... ERR
    • sigspecがERRの場合、コマンドargは、パイプライン(単一の単純なコマンドで構成されている場合があります)、リスト、または複合コマンドがゼロ以外の終了ステータスを返すたびに実行されます。
      • ERRの失敗したコマンドは、すぐに次のコマンドリストの一部である場合、トラップが実行されていないwhileか、untilキーワードは...
      • ... if文のテストの一部...
      • ...で実行したコマンドの一部&&または||最後の次のコマンドを除いて、リスト&&またはを||...
      • ...パイプライン内の任意のコマンドが最後に...
      • ...またはコマンドの戻り値がを使用して反転されて!いる場合
    • これらは、errexit -eオプションが従うのと同じ条件です。

上記のERRトラップは、他のコマンドの戻り値の評価に関するものであることに注意してください。ただし、展開エラーが発生した場合、何かを返すコマンドは実行されません。あなたの例でecho 決して起こりません -シェルが引数を評価して展開し-uている間、nset変数に遭遇するためです

そして、もしあればEXITトラップが実行され、シェルは診断メッセージと0以外の終了ステータスで終了します-まさにそうすべきです。

用としてRC:0の事、私はいくつかの種類のバージョンの特定のバグである期待する-おそらくのための2つのトリガーをどうするEXIT同時に発生したとの他の終了コードを取得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コマンドが間違った行番号を持っています。
  • 非同期サブシェルコマンドでSIGINTおよびSIGQUITが無効になるバグを修正しましたtrap
  • 対話型シェルで2番目以降のSIGINTが無視される割り込み処理の問題を修正しました。
  • シェルはtrap、それらのシグナルのハンドラーの実行中にシグナルの受信をブロックしなくなり、ほとんどの trapハンドラーを再帰的に実行できます (ハンドラーの実行trap中にtrapハンドラーを実行)

最も関連性が高いのは最後または最初のいずれか、またはおそらく2つの組み合わせです。trapハンドラは、その非常に自然である非同期そのジョブ全体を待って処理することですので、非同期信号を。そして、あなたが、同時に2つの引き金-eu$UNSET_VAR

そのため、単に更新する必要があるかもしれませんが、自分が好きなら、まったく別のシェルでそれを行うでしょう。


パラメーターの展開が異なる方法で処理される方法の説明をありがとう。それは私に多くのものをクリアしました。
dvdgsng

あなたの説明が最も役に立ったので、私はあなたに報奨金を与えています。
dvdgsng

@dvdgsng-グラシアス。好奇心から、あなたはあなたの解決策で現れたことがありますか?
mikeserv

9

(私は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

私のバッシュで私は得る

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

素晴らしい、実際に価値を追加する実用的なソリューション!
フロリアンハイグル
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.