web-dev-qa-db-ja.com

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

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

1)set -uはERRトラップをトリガーしません

  • コード:

    #!/bin/bash
    trap 'echo "ERR (rc: $?)"' ERR
    set -u
    echo ${UNSET_VAR}
    
  • 予期される:ERRトラップが呼び出される、RC!= 0
  • 実際:ERRトラップは呼び出されないと呼ばれる、RC == 1
  • 注:set -eは結果を変更しません

2)set -euを使用すると、EXITトラップの終了コードが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を返します。
  • 編集:SOこのトピックに関する投稿に 興味深いコメント があることを示唆していますこれは、使用されているBashのバージョンに関連している可能性があります。このスニペットをBash 4.3.11でテストすると、RC = 1になるので、それが望ましいです。残念ながら、現時点では、すべてのホストでBash(3.2.51から)をアップグレードすることはできません。他の解決策を考え出す必要があります。

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

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

29
dvdgsng

_man bash_から:

  • _set -u_
    • 特別なパラメーター_"@"_および_"*"_以外の未設定の変数およびパラメーターは、パラメーター展開を実行するときにエラーとして扱います。設定されていない変数またはパラメーターに対して展開が試行された場合、シェルはエラーメッセージを出力し、そうでない場合は_-i_ nteractiveの場合、ゼロ以外のステータスで終了します。

POSIXは、拡張エラーが発生した場合、非インタラクティブシェルは拡張がいずれかに関連付けられている場合に終了すると述べていますシェルの特別な組み込み(これはbashの区別ですが、とにかく定期的に無視されるため、おそらく無関係です)またはその他のユーティリティ。

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

また、_man bash_から:

  • _trap ... ERR_
    • Sigspecが[〜#〜] err [〜#〜]の場合、コマンドargはパイプライン(単一の単純なコマンドで構成される場合があります)、リスト、または複合コマンドは、次の条件に従って、ゼロ以外の終了ステータスを返します:
      • [〜#〜] err [〜#〜]トラップは、失敗したコマンドがwhileまたはuntilキーワードの直後のコマンドリストの一部である場合は実行されません...
      • ...ifステートメントのテストの一部...
      • ...最後の_&&_または_||_...に続くコマンドを除く、_&&_または_||_リストで実行されるコマンドの一部...
      • ...パイプライン内の任意のコマンドですが、最後の...
      • ...または、コマンドの戻り値が_!_を使用して反転されている場合。
    • これらは、errexit_-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によって生成されたエラーメッセージに誤った行番号が含まれるバグを修正しました。
  • [〜#〜] sigint [〜#〜]および[〜#〜] sigquit [〜#〜]非同期サブシェルコマンドでtrappableになりません。
  • 2番目以降の[〜#〜] sigint [〜#〜]がインタラクティブシェルによって無視される原因となった割り込み処理の問題を修正しました。
  • シェルは、これらのシグナルのtrapハンドラーの実行中にシグナルの受信をブロックせず、mosttrapハンドラーを再帰的に実行できるようにします(実行中にtrapハンドラーを実行します) trapハンドラが実行中です)

最も関連性が高いのは、最後か最初のどちらか、またはおそらく2つの組み合わせです。 trap handlerは、その性質上asynchronousです。これは、そのジョブ全体が待機して処理するためです非同期信号。そして、_-eu_と_$UNSET_VAR_で同時に2つをトリガーします。

更新する必要があるかもしれませんが、気に入った場合は、別のシェルで完全に更新します。

16
mikeserv

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