変数割り当ての戻りステータスはどのように決定されますか?


10

私はこのようなスクリプトの構成を見てきました:

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

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

回答:


12

これは、(POSIXの場合) Open Group Base仕様のセクション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) baz                            Exit status from "baz"
$ foo $(bar)                                Exit status from "foo"

これもbashの仕組みです。ただし、最後の「それほど単純ではない」セクションも参照してください。

phk、彼の質問では、割り当ては、コマンド置換がある場合を除いて、終了ステータスを持つコマンドのようなものですか?、提案

...割り当て自体がコマンドとしてカウントされるように見えます...終了値がゼロですが、割り当ての右側の前に適用されます(たとえば、コマンド置換呼び出し...)

それはひどい見方ではありません。単純なコマンドのリターン・ステータスを決定するための粗スキーム(一方は含有しない;&|&&または||)です。

  • 最後またはコマンドワード(通常はプログラム名)に達するまで、行を左から右にスキャンします。
  • 変数の割り当てが表示される場合、行の戻りステータスは0である可能性があります。
  • コマンドの置換が表示された場合、つまり、$(…)そのコマンドから終了ステータスを取得します。
  • (コマンド置換ではなく)実際のコマンドに到達した場合は、そのコマンドの終了ステータスを取得します。
  • 回線の戻り状況は、最後に遭遇した番号です。
    コマンドの引数としてのコマンド置換、たとえば、foo $(bar)はカウントされません。から終了ステータスを取得しますfoophkの表記を言い換えると、ここでの動作は

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

しかし、これは少し単純化しすぎています。からの全体的な返品ステータス

A = $(cmd 1)B = $(cmd 2)C = $(cmd 3)D = $(cmd 4)E = mc 2
からの終了ステータスです。後に発生した割り当て割り当ては0に全体的な終了ステータスを設定しません。cmd4E=D=

イカロスはphkの質問に対する彼の回答で、重要な点を挙げています。変数は読み取り専用として設定できます。POSIX標準のセクション2.9.1の最後から3番目の段落は、

変数割り当てのいずれかが、現在のシェル環境でreadonly属性が設定されている変数に値を割り当てようとすると(割り当てがその環境で行われたかどうかに関係なく)、変数割り当てエラーが発生します。これらのエラーの影響については、シェルエラーの影響を参照してください。

だからあなたが言うなら

readonly A
C=Garfield A=Felix T=Tigger

リターン・ステータスは1です。それ問題ではない場合は、文字列GarfieldFelixおよび/またはTigger コマンド置換(S)で置き換えられている-しかし、下記の注意事項を参照してください。

セクション2.8.1シェルエラーの結果には、別の一連のテキストと表があり、

対話型シェルが終了しないようにする必要がある表に示されているすべてのケースで、シェルは、エラーが発生したコマンドの以降の処理を実行してはなりません。

詳細のいくつかは理にかなっています。一部はしません:

  • A=割り当ては時々中止し、その最後の文は、指定しているようだとして、コマンドラインを。上記の例でCは、はに設定されていますがGarfield、設定されていTません(もちろん、どちらも設定されていません  A)。
  • 同様に、 実行します が、実行しません。しかし、(4.1.Xおよび4.3.Xを含む)にはbashの私のバージョンでは、それがない実行します。(ちなみに、これは、割り当ての終了値が割り当ての右側の前に適用されるというphkの解釈にさらに弾みをつけます。)C=$(cmd1) A=$(cmd2) T=$(cmd3)cmd1cmd3
    cmd2

しかし、ここに驚きがあります:

私のバージョンのbashでは、

読み取り専用A
C = 何か A = 何か T = 何か cmd 0

実行。特に、cmd0

C = $(cmd 1)A = $(cmd 2)T = $(cmd 3cmd 0
andを 実行しますが、は実行しません。(これはコマンドがない場合の動作とは逆です。)そして、の環境で(および)を設定します。これはbashのバグかと思います。cmd1cmd3cmd2TCcmd0


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

この回答の最初の段落は、「単純なコマンド」に言及しています。  仕様によると、

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

これらは、最初のサンプルブロックのステートメントと同様です。

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

最初の3つには変数の割り当てが含まれ、最後の3つにはコマンドの置換が含まれます。

しかし、一部の変数の割り当てはそれほど単純ではありません。  bash(1)は言う、

代入文もの引数として表示されることがありaliasdeclaretypesetexportreadonly、およびlocalコマンド(組み込み宣言コマンド)。

についてexport、POSIX仕様によれば、

終了ステータス

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

そしてPOSIXはをサポートしていませんlocalが、bash(1)はこう言っています、

local関数内でない場合に使用するとエラーになります。local関数の外で使用したり、無効な名前を指定したり、名前が読み取り専用変数である場合を除き、戻りステータスは0 です。

行の間を読むと、次のような宣言コマンドがわかります

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

幸いにもShellCheckは、エラーをキャッチし、提起SC2155ことをアドバイスし、

export foo="$(mycmd)"

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

foo=$(mycmd)
export foo

そして

local foo="$(mycmd)"

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

local foo
foo=$(mycmd)

1
よかった。ありがとう!ボーナスポイントについて、これとどのようにlocal結びついているか知っていますか?例えばlocal foo=$(bar)
ワイルドカード2016年

1
第二の例の場合は(ちょうどFOO=$(bar))それの価値は理論的に両方の終了ステータスことを指摘し、割り当てが役割を果たしているかもしれませんが、参照unix.stackexchange.com/a/341013/117599
PHK

1
@ワイルドカード:気に入っていただけてうれしいです。もう一度更新しました。あなたが読んだバージョンの大部分が間違っていました。あなたがここにいる限り、あなたはどう思いますか?これは、読み取り専用であってもA=foo cmd実行されるbashのバグですか?cmdA
G-Manは 'Reinstate Monica'

1
@phk:(1)興味深い理論ですが、それがどのように意味をなすかはわかりません。「サプライズ」という見出しの直前の例をもう一度見てみましょう。場合A読み取り専用であり、コマンド・C=value₁ A=value₂ T=value₃セットCではなくT(とは、もちろん、A設定されていない) -シェルが無視し、コマンドラインの処理終了T=value₃、ためA=value₂のエラーです。(2)Stack Overflowの質問へのリンクをありがとう—私はそれにコメントを投稿しました。
G-Manは 'Reinstate Monica'

1
@ワイルドカード「ボーナスポイントについては、ローカルがこれにどのように結びついているか知っていますか?」。はい... (manページから)の理由でlocal=$(false)終了値があります。これは、それをそのように設計した天才にとって、世界での十分な荒らしではありません。0It is an error to use local when not within a function. The return status is 0 unless local is used outside a function, an invalid name is supplied, or name is a readonly variable.
デビッドトンホーファー

2

Bash(LESS=+/'^SIMPLE COMMAND EXPANSION' bash)で文書化されています:

展開後にコマンド名が残っていると…。それ以外の場合、コマンドは終了します。...コマンド置換がなかった場合、コマンドはステータス0で終了します。

言い換えれば(私の言葉):

展開後にコマンド名が残っておらず、コマンド置換が実行されなかった場合、コマンドラインはステータス0で終了します。

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