web-dev-qa-db-ja.com

Errtraceとローカル

次のスクリプトを使用しています。

_#!/bin/bash -Eu

trap 'echo Hi' ERR

exit_failure() {
  echo "Hello, World!"
  return 1
}

sub_failure() {
  res=$(exit_failure)
}

sub_failure
_

その結果、次のようになります。

_Hi
Hi
_

ただし、sub_failure()を次のように変更すると次のようになります。

_sub_failure() {
  local res=$(exit_failure)
}
_

出力がありません。 ERRはもうトラップされていませんか?なぜ信号が隠されているのですか?ローカル変数を使用したい場合、どうすればERRをトラップできますか? local res; res=$(exit_failure)を実行できることはわかっていますが、なぜ両方を分離する必要があるのですか?

4

バグではありません。これは実際に定義された動作です。

_bash -Eux_を使用すると、何が起こるかを確認できます。 (_-Eu_あなたのシバンから+ _-x_)

_+ trap 'echo Hi' ERR
+ sub_failure
++ exit_failure
++ echo 'Hello, World!'
++ return 1
+++ echo Hi
+ res='Hello, World!
Hi'
++ echo Hi
Hi
++ echo Hi
Hi
_

コマンド置換を行う場合、_-E_スイッチのためにtrapが継承されます。したがって、exit_failure()関数の_return 1_によってトリガーされる継承されたトラップからの「Hi」は、retに格納されている値の一部になります。 (これは、localを使用してバリアントを実行する場合にも当てはまります)

さらに、_res=..._式は_1_(エラー)を返し、トラップをトリガーします(sub_failure()関数内)。

_res=..._は_1_を返し、関数の結果は関数の最後のコマンドの結果であるため、sub_failure()の結果も_1_(エラー)であり、メインシェルで_sub_failure_が実行された後、トラップが再度トリガーされます。したがって、2つの表示可能な「Hi」を取得します。1つは_res=...._用、もう1つは_sub_failure_用、非表示の「Hi」は_$res_に格納されます。

localバリアントの場合:

_+ trap 'echo Hi' ERR
+ sub_failure
++ exit_failure
++ echo 'Hello, World!'
++ return 1
+++ echo Hi
+ local 'res=Hello, World!
Hi'
_

定義上、localは、関数で使用されると常に_0_を返します。非表示の「Hi」を_local res=..._に格納したまま、_0_を_$res_(成功)と評価します。また、_res=.._は_0_に評価されるため、_sub_failure_も_0_を返します。したがって、今回は「隠された」失敗が1回、成功が2回発生します。

このスレッドが静かな古いものであっても、これが役立つことを願っています;)

また、_local res=..._を分割する理由も明確にする必要があります

_local res
res=....
_

最初のバリアントの動作を復元しますか? ;)

4
mariux