「-o errtrace」を使用したコマンド置換でのエラーのトラップ(つまり、-Eを設定)


14

この参照マニュアルによると:

-E(-o errtraceも)

設定すると、ERRのトラップは、シェル関数、コマンド置換、およびサブシェル環境で実行されるコマンドに継承されます。このような場合、ERRトラップは通常継承されません。

ただし、以下が機能しないため、間違って解釈する必要があります。

#!/usr/bin/env bash
# -*- bash -*-

set -e -o pipefail -o errtrace -o functrace

function boom {
  echo "err status: $?"
  exit $?
}
trap boom ERR


echo $( made up name )
echo "  ! should not be reached ! "

私はすでに簡単な割り当てを知っていmy_var=$(made_up_name)ます、でスクリプトを終了しますset -eます。で(つまり、errexit)。

され -E/-o errtrace、上記のコードのような作品になって?または、ほとんどの場合、私はそれを読み違えましたか?


2
これはいい質問です。に置き換えるecho $( made up name )$( made up name )、目的の動作が生成されます。私には説明がありません。
iruvar

bashの-Eについては知りませんが、エラーがパイプラインの最後のコマンドの結果である場合にのみ-eがシェルの終了に影響することを知っています。あなたvar=$( pipe )$( pipe )例は両方ともパイプのエンドポイントを表しますが、pipe > echoそうではありません。私のマニュアルページには、「1。マルチコマンドパイプラインの個々のコマンドが失敗しても、シェルは終了しません。パイプライン自体の失敗のみが考慮されます。」
mikeserv 14年

ただし、失敗させることもできます:echo $($ {madeupname?})。しかし、それは設定されています-e。繰り返しますが、-Eは私の経験の範囲外です。
mikeserv 14年

@mikeserv @ 1_CR bashのマニュアル@ エコーがあることを示しecho...いつもこれは分析に織り込まれなければならない0を返す

回答:


5

注:zshここでの例のほとんどで「インラインコメント」を受け入れるように設定しないと、「悪意のあるパターン」について文句を言い、プロキシシェルを介して実行しませんsh <<-\CMD

さて、上記のコメントで述べたように、私はbashset -Eについて具体的には知りませんが、POSIX互換シェルは、必要に応じて値をテストする簡単な手段を提供することを知っています:

    sh -evx <<-\CMD
    _test() { echo $( ${empty:?error string} ) &&\
        echo "echo still works" 
    }
    _test && echo "_test doesnt fail"
    # END
    CMD
sh: line 1: empty: error string
+ echo

+ echo 'echo still works'
echo still works
+ echo '_test doesnt fail'
_test doesnt fail

あなたは、私が使用してもいることがわかります上記のparameter expansionテストに${empty?} _test()まだ return、最後にevincedされるよう-パスechoこれが失敗した値は殺すために発生$( command substitution )-それが含まれているサブシェルを、その親シェル_test、この時点で-トラック輸送を続けています。そしてecho気にしない-それはテストで\newline; echoないだけを提供することは十分に幸せです。

しかし、これを考慮してください:

    sh -evx <<-\CMD
    _test() { echo $( ${empty:?error string} ) &&\
            echo "echo still works" ; } 2<<-INIT
            ${empty?function doesnt run}
    INIT
    _test ||\
            echo "this doesnt even print"
    # END
    CMD
_test+ sh: line 1: empty: function doesnt run

_test()'sでは事前に評価されたパラメーターを入力に入力したためINIT here-document_test()関数はまったく実行しようとさえしません。さらに、shシェルは明らかにゴーストを完全に放棄し、echo "this doesnt even print" 印刷さえしません。

おそらくそれはあなたが望むものではありません。

これは、${var?}スタイルパラメータ拡張が、パラメータが欠落した場合に終了するように設計されてshellいるために発生します。これは次のように機能します

${parameter:?[word]}

場合、エラーを示すNullか、Unset.パラメータが未設定またはヌルの場合、expansion of wordでなければならない(または単語が省略された場合、それを示すメッセージが設定されていない)written to standard errorshell exits with a non-zero exit statusそれ以外の場合、の値parameter shall be substituted対話型シェルを終了する必要はありません。

ドキュメント全体をコピー/貼り付けすることはしませんが、set but null値に失敗したい場合は、次の形式を使用します。

${var :? error message }

:colon上記のように。必要に応じてnull値が成功するために、ちょうどコロンを省略します。すぐに表示するように、設定値に対してのみ無効にして失敗することもできます。

別の実行 _test():

    sh <<-\CMD
    _test() { echo $( ${empty:?error string} ) &&\
            echo "echo still works" ; } 2<<-INIT
            ${empty?function doesnt run}
    INIT
    echo "this runs" |\
        ( _test ; echo "this doesnt" ) ||\
            echo "now it prints"
    # END
    CMD
this runs
sh: line 1: empty: function doesnt run
now it prints

これはあらゆる種類のクイックテストで機能しますが、上記のよう_test()に、pipeline失敗の真ん中から実行され、実際、そのcommand listサブシェルを含むサブシェルは完全に失敗します。関数内のコマンドも実行もecho現在は印刷されているため、簡単にテストできることも示されていますecho "now it prints"

悪魔は細部に宿っていると思います。上記の場合、終了するシェルはスクリプトではなく_main | logic | pipeline( subshell in which we ${test?} ) ||これほど少ないのサンドボックスが要求されます。

そして、それは明らかではないかもしれませんが、反対の場合のみ、またはset=値のみを渡したい場合、それもかなり簡単です:

    sh <<-\CMD
    N= #N is NULL
    _test=$N #_test is also NULL and
    v="something you would rather do without"    
    ( #this subshell dies
        echo "v is ${v+set}: and its value is ${v:+not NULL}"
        echo "So this ${_test:-"\$_test:="} will equal ${_test:="$v"}"
        ${_test:+${N:?so you test for it with a little nesting}}
        echo "sure wish we could do some other things"
    )
    ( #this subshell does some other things 
        unset v #to ensure it is definitely unset
        echo "But here v is ${v-unset}: ${v:+you certainly wont see this}"
        echo "So this ${_test:-"\$_test:="} will equal NULL ${_test:="$v"}"
        ${_test:+${N:?is never substituted}}
        echo "so now we can do some other things" 
    )
    #and even though we set _test and unset v in the subshell
    echo "_test is still ${_test:-"NULL"} and ${v:+"v is still $v"}"
    # END
    CMD
v is set: and its value is not NULL
So this $_test:= will equal something you would rather do without
sh: line 7: N: so you test for it with a little nesting
But here v is unset:
So this $_test:= will equal NULL
so now we can do some other things
_test is still NULL and v is still something you would rather do without

上記の例では、4つの形式のPOSIXパラメーター置換とそれらのさまざまな:colon nullor not nullテストを利用しています。上記のリンクに詳細情報がありますが、ここにもあります。

そして、_test関数の動作も示すべきだと思いますか?empty=something関数のパラメーターとして(または事前にいつでも)宣言するだけです。

    sh <<-\CMD
    _test() { echo $( echo ${empty:?error string} ) &&\
            echo "echo still works" ; } 2<<-INIT
            ${empty?tested as a pass before function runs}
    INIT
    echo "this runs" >&2 |\
        ( empty=not_empty _test ; echo "yay! I print now!" ) ||\
            echo "suspiciously quiet"
    # END
    CMD
this runs
not_empty
echo still works
yay! I print now!

この評価は独立していることに注意する必要があります。失敗するために追加のテストは必要ありません。さらにいくつかの例:

    sh <<-\CMD
    empty= 
    ${empty?null, no colon, no failure}
    unset empty
    echo "${empty?this is stderr} this is not"
    # END
    CMD
sh: line 3: empty: this is stderr

    sh <<-\CMD
    _input_fn() { set -- "$@" #redundant
            echo ${*?WHERES MY DATA?}
            #echo is not necessary though
            shift #sure hope we have more than $1 parameter
            : ${*?WHERES MY DATA?} #: do nothing, gracefully
    }
    _input_fn heres some stuff
    _input_fn one #here
    # shell dies - third try doesnt run
    _input_fn you there?
    # END
    CMD
heres some stuff
one
sh: line :5 *: WHERES MY DATA?

そして最後に、元の質問に戻ります。サブシェルでエラーを処理する方法は$(command substitution)真実は-2つの方法がありますが、どちらも直接的なものではありません。問題の核心はシェルの評価プロセスです-シェル拡張($(command substitution))は、現在のシェルコマンドの実行よりもシェルの評価プロセスの早い段階で発生します。

opが経験する問題は、現在のシェルがエラーを評価するまでに、$(command substitution)サブシェルがすでに置き換えられていることです。エラーは残っていません。

では、2つの方法は何ですか?どちらかで明示的に行う$(command substitution)サブシェル、テストなしでテストするか、その結果を現在のシェル変数に吸収してその値をテストします。

方法1:

    echo "$(madeup && echo \: || echo '${fail:?die}')" |\
          . /dev/stdin

sh: command not found: madeup
/dev/stdin:1: fail: die

    echo $?

126

方法2:

    var="$(madeup)" ; echo "${var:?die} still not stderr"

sh: command not found: madeup
sh: var: die

    echo $?

1

これは、行ごとに宣言された変数の数に関係なく失敗します。

   v1="$(madeup)" v2="$(ls)" ; echo "${v1:?}" "${v2:?}"

sh: command not found: madeup
sh: v1: parameter not set

そして、戻り値は一定のままです。

    echo $?
1

今トラップ:

    trap 'printf %s\\n trap resurrects shell!' ERR
    v1="$(madeup)" v2="$(printf %s\\n shown after trap)"
    echo "${v1:?#1 - still stderr}" "${v2:?invisible}"

sh: command not found: madeup
sh: v1: #1 - still stderr
trap
resurrects
shell!
shown
after
trap

    echo $?
0

たとえば、実際には彼の正確な仕様:echo $ {v = $(madeupname)}; echo "$ {v:?trap1st}!に到達しません!"
mikeserv 14年

1
要約すると、これらのパラメーター展開は、変数が設定されているかどうかなどを制御するための優れた方法echo "abc" "${v1:?}"です。そして、シェルは1を返します。これは、コマンド("${v1:?}"cliで直接)の有無にかかわらずtrue です。しかし、OPのスクリプトの場合、トラップをトリガーするために必要なのは、存在しないコマンドの代替を含む変数割り当てを1行に配置することだけです。それ以外の場合、エコーの動作は、説明したテストのように中断されない限り、常に0を返します。

ただv=$( madeup )。何が危険なのかわかりません。それは単なる割り当てであり、人は例えばコマンドのスペルを間違えましたv="$(lss)"。エラー。はい、最後のコマンド$のエラーステータスで確認できます。-これは、行のコマンド(コマンド名のない割り当て)であり、他には何もない-エコーの引数ではないため。さらに、ここでは関数によって!= 0としてトラップされるため、フィードバックが2回得られます。そうでなければ、確かにあなたが説明するように、フレームワーク内でこれを整然と行うより良い方法がありますが、OPには1行がありました:エコーと彼の失敗した置換。彼はエコーについて疑問に思っていました。

はい、単純な割り当てが質問で言及されており、当然のことですが、-Eの使用方法に関する具体的なアドバイスを提供することはできませんでしたが、実証された問題と同様の結果を表示することはできませんでしたバシズム。いずれにせよ、パイプラインでこのようなソリューションを処理するのが困難になるのは、具体的には、1行あたり1つの割り当てなど、あなたが言及する問題です。あなたの言うことは
mikeserv 14年

1
良い点-おそらくもっと努力が必要です。それについて考えます。
mikeserv 14年

1

設定されている場合、ERRのトラップは、シェル関数、コマンド置換、およびサブシェル環境で実行されるコマンドに継承されます

スクリプトでは、コマンド実行(echo $( made up name ))。bashコマンドでは、どちらかで区切られますまたは改行で。コマンドで

echo $( made up name )

$( made up name )コマンドの一部と見なされます。この部分が失敗してエラーが返された場合でも、コマンド全体が正常に実行されechoます。コマンドが0で戻るため、トラップはトリガーされません。

割り当てとエコーの2つのコマンドに入れる必要があります

var=$(made_up_name)
echo $var

echo $var失敗しません- 実際にそれを見る$var前に空に展開されますecho
mikeserv 14年

0

これは、bashのバグが原因です。コマンド置換ステップ中

echo $( made up name )

madeサブシェルで実行されます(または見つかりません)が、サブシェルは親シェルからのトラップを使用しないように「最適化」されます。これはバージョン4.4.5で修正されました。

特定の状況では、単純なコマンドが最適化されてフォークが削除され、EXITトラップが実行されなくなります。

bash 4.4.5以降では、次の出力が表示されます。

error.sh: line 13: made: command not found
err status: 127
  ! should not be reached !

トラップハンドラーが期待どおりに呼び出され、サブシェルが終了します。(set -e親ではなくサブシェルのみが終了するため、実際には「到達しない」メッセージに到達する必要があります。)

古いバージョンの回避策は、最適化されていない完全なサブシェルを強制的に作成することです。

echo $( ( made up name ) )

余分なスペースは、算術展開と区別するために必要です

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.