変数割り当ての戻り状態はどのように決定されますか?

変数割り当ての戻り状態はどのように決定されますか?

スクリプトで次の設定を見たことがあります。

if somevar="$(somecommand 2>/dev/null)"; then
...
fi

これはどこかに文書化されていますか?変数の戻り状態はどのように決定され、コマンド置換とどのように関連していますか? (たとえば、同じ結果が得られますかif echo "$(somecommand 2>/dev/null)"; then?)

答え1

文書化されています(POSIXの場合)2.9.1 簡単なコマンド グループ基本仕様を公開します。そこにはテキストの壁があります。最後の段落に注意してください。

コマンド名がある場合は、次の説明に従って実行を続行する必要があります。命令の検索と実行。コマンド名はありませんが、コマンドにコマンド置換が含まれている場合、コマンドは最後に実行されたコマンド置換の終了状態で完了する必要があります。それ以外の場合、コマンドはゼロ終了状態で完了する必要があります。

例えば、

   Command                                         Exit Status
$ FOO=BAR                                   0 (but see also the note from icarus, below)
$ FOO=$(bar)                                Exit status from "bar"
$ FOO=$(bar)$(quux)                         Exit status from "quux"
$ FOO=$(bar) baz                            Exit status from "baz"
$ foo $(bar)                                Exit status from "foo"

bashもこのように動作します。ただし、最後の「あまり単純ではない」セクションも参照してください。

PHK、彼の質問に割り当ては、コマンド置換がない限り、終了状態を持つコマンドと同じです。提案

...割り当て自体がコマンドと見なされているようです...終了値は0ですが、割り当ての右側に適用されます(たとえば、コマンド代替呼び出し...)

それは問題を見る恐ろしい方法ではありません。単純なコマンド(;&または|除く)&&の戻り状態を決定するおおよその方法は次のとおり||です。

  • 終了またはコマンドワード(通常プログラム名)に達するまで、左から右に行をスキャンします。

  • 変数の割り当てが表示された場合、その行の戻り状態はおそらくゼロです。

  • コマンド置換が表示されたら、$(…)そのコマンドの終了ステータスを取得します。

  • 実際のコマンドに達すると(コマンド置換ではない)、そのコマンドの終了ステータスを取得します。

  • 行の戻り状態は、最後に発生した数値です。
    コマンドの置き換えパラメータとしてなどのコマンドの場合は、foo $(bar)終了ステータスを確認できますfoo。義役phk表記、ここでの動作は次のとおりです。

      temporary_variable  = EXECUTE( "bar" )
      overall_exit_status = EXECUTE( "foo", temporary_variable )
    

しかし、これは少し単純すぎます。全体的な返品ステータスは、以下に由来します。

A = $(コマンド1)B = $(コマンド2)C = $(コマンド3)D = $(コマンド4)E = MC 2
終了状態です。割り当て後に発生する割り当ては、完全終了ステータスをゼロに設定しません。cmd4E=D=

イカルス、存在するphkの質問に対する彼の答え、重要なポイントを提示します。変数は読み取り専用に設定できます。最後の段落で3番目POSIX標準セクション2.9.1説明する、

変数の割り当てがその変数に値を割り当てようとした場合読み取り専用現在のシェル環境でプロパティが設定されている場合(その環境で割り当てられているかどうかにかかわらず)、変数割り当てエラーが発生します。バラよりシェルエラーの結果これらの間違いの結果について。

だからあなたが言うなら

readonly A
C=Garfield A=Felix T=Tigger

Garfield戻り状態は 1 です。文字列Felixおよび/またはTigger コマンド置換で置き換えられるかどうかは重要ではありません。ただし、以下の注意事項をご覧ください。

2.8.1 シェルエラーの結果別のテキストの束とテーブルがあり、次に終了します。

対話型シェルを終了しない表に示されているすべての場合、シェルはエラーが発生したコマンドを処理しなくてはなりません。

いくつかの詳細は意味がありますが、一部は意味がありません。

  • A=時々宿題をするやめる最後の文が指定するようにコマンドラインです。上記の例では、はにC設定されていますが、設定されていません(もちろん両方が設定されていません)。GarfieldTA
  • 繰り返しますが、実行は行われますが、実行はできません。C=$(cmd1) A=$(cmd2) T=$(cmd3)cmd1cmd3
    しかし、私のbashバージョン(4.1.Xと4.3.Xを含む)では、する実装する。 (ところで、これは、割り当ての終了値が割り当ての右前に適用されるというphkの解釈をさらに弾劾します。)cmd2

しかし、驚くべきことは次のとおりです。

私のbashバージョンでは

読み取り専用A
C=A =時間= コマンド0

する実装する。特に、cmd0

C = $(コマンド1)A = $(コマンド2)ティー= $(コマンド3)   コマンド0
実行して実行しますが、実行しません。 (これはコマンドのない動作と反対であることに注意してください。) そして の環境にも設定されます。これがbashのバグかどうか疑問に思います。cmd1cmd3cmd2TCcmd0


それほど単純ではありません。

この回答の最初の段落は「簡単なコマンド」を表します。  仕様説明する、

「単純なコマンド」は、任意の順序で任意の変数割り当ておよびリダイレクトのシーケンスであり、任意にワードおよびリダイレクトが続き、制御演算子によって終了される。

この文は、最初のブロック例の文と似ています。

$ FOO=BAR
$ FOO=$(bar)
$ FOO=$(bar) baz
$ foo $(bar)

最初の3つは変数の代入を含み、最後の3つはコマンド置換を含みます。

しかし、いくつかの変数の割り当てはそれほど単純ではありません。  大きな打撃(1)説明する、

割り当てステートメントはパラメーターとして表示されることもあります。aliasdeclaretypesetexportreadonlylocal組み込みコマンド(宣言注文する)。

のためにexportPOSIX仕様説明する、

終了ステータス

    0
      すべての名前オペランドを正常にエクスポートしました。
    >0
      1つ以上の名前をエクスポートできないか、-pオプションが指定されてエラーが発生しました。

POSIXはサポートされていlocalませんが、大きな打撃(1)説明する、

使用中にエラーが発生しました。local関数の内部ではないとき。戻り状態はゼロ以外のゼロです。local関数外で使用、無効名前提供する名前読み取り専用変数です。

行間を読むと、宣言されたコマンドが次のようになります。

export FOO=$(bar)

そして

local FOO=$(bar)

より良い

foo $(bar)

終了ステータスを無視し、bar 基本コマンド(exportlocalまたは)に基づいてfoo終了ステータスを提供する限り。だからこんな奇妙な現象が現れます

   Command                                           Exit Status
$ FOO=$(bar)                                    Exit status from "bar"
                                                  (unless FOO is readonly)
$ export FOO=$(bar)                             0 (unless FOO is readonly,
                                                  or other error from “export”)
$ local FOO=$(bar)                              0 (unless FOO is readonly,
                                                  statement is not in a function,
                                                  or other error from “local”)

我々はそれを証明するために使用することができます

$ export FRIDAY=$(date -d tomorrow)
$ echo "FRIDAY   = $FRIDAY, status = $?"
FRIDAY   = Fri, May 04, 2018  8:58:30 PM, status = 0
$ export SATURDAY=$(date -d "day after tomorrow")
date: invalid date ‘day after tomorrow’
$ echo "SATURDAY = $SATURDAY, status = $?"
SATURDAY = , status = 0

そして

myfunc() {
    local x=$(echo "Foo"; true);  echo "x = $x -> $?"
    local y=$(echo "Bar"; false); echo "y = $y -> $?"
    echo -n "BUT! "
    local z; z=$(echo "Baz"; false); echo "z = $z -> $?"
}

$ myfunc
x = Foo -> 0
y = Bar -> 0
BUT! z = Baz -> 1

幸いなことに住宅検査エラーをキャッチして上げるSC2155、これは次のことを示します。

export foo="$(mycmd)"

に変更する必要があります

foo=$(mycmd)
export foo

そして

local foo="$(mycmd)"

に変更する必要があります

local foo
foo=$(mycmd)

クレジットと参考資料

$(bar)$(quux)Gillesの回答から、コマンド置換をリンクするアイデアを得ました。 Pipefailと同様にバックティックが失敗した場合にbashを終了するにはどうすればよいですか?、この問題に関連する多くの情報が含まれています。

答え2

LESS=+/'^SIMPLE COMMAND EXPANSION' bashBash() に文書化されています。

拡張後も命令名が残っている場合....そうでなければ命令は終了します。 ...コマンドを置換せずに、コマンドはステータス0で終了します。

つまり(私の言葉は):

拡張後にコマンド名が残らず、コマンド置換が実行されない場合、コマンドラインはゼロ状態で終了します。

関連情報