web-dev-qa-db-ja.com

Bashのコマンド置換への変数割り当ての終了コード

私は、変数代入を単純にコマンド置換で実行すると、コマンドがどのエラーコードを返すかについて混乱しています:

_a=$(false); echo $?
_

_1_を出力します。これにより、変数の割り当てが最後のコードでスイープしたり新しいエラーコードを生成したりすることはありません。しかし、私がこれを試したとき:

_false; a=""; echo $?
_

それは_0_を出力します。これは明らかに_a=""_が返すものであり、falseによって返される_1_をオーバーライドします。

これが起こる理由を知りたいのですが、変数の割り当てに他の通常のコマンドとは異なる特殊性はありますか?または、単にa=$(false)が単一のコマンドであると見なされ、コマンド置換部分のみが理にかなっていますか?

-UPDATE-

みんなありがとう、答えとコメントから、「コマンド置換を使用して変数を割り当てると、終了ステータスはコマンドのステータスになります」というポイントを得ました。 (@Barmarによる)、この説明は非常に明確で理解しやすいですが、話すのはプログラマにとって十分に正確ではありません.TLDPまたはGNUなどの当局からこの点の参照を見たいです= manページ、見つけてくれてありがとう、ありがとう!

45
Reorx

$(command)としてコマンドを実行すると、 コマンドの出力自体を置き換える が許可されます。

あなたが言う時:

a=$(false)             # false fails; the output of false is stored in the variable a

コマンドfalseによって生成された出力は、変数aに格納されます。さらに、終了コードはコマンドによって生成されたものと同じです。 help falseは次のことを示します。

false: false
    Return an unsuccessful result.

    Exit Status:
    Always fails.

一方、言って:

$ false                # Exit code: 1
$ a=""                 # Exit code: 0
$ echo $?              # Prints 0

aへの割り当ての終了コード(0)が返されます。


編集:

manual からの引用:

拡張の1つにコマンド置換が含まれていた場合、コマンドの終了ステータスは、最後に実行されたコマンド置換の終了ステータスです。

BASHFAQ/002 からの引用:

変数にコマンドの戻り値や出力を保存するにはどうすればよいですか?

...

output=$(command)

status=$?

outputへの割り当ては、commandの終了ステータスに影響を与えません。終了ステータスは、まだ$?にあります。

57
devnull

関数内でlocalを使用する場合はそうではないことに注意してください。これは、受け入れられた回答に記載されている動作と、ここに投稿されたリンクとは微妙に異なる動作です。 http://mywiki.wooledge.org/BashFAQ/002

たとえば、次のbashスクリプトをご覧ください。

#!/bin/bash
function funWithLocalVar() {
    local output="$(echo "Doing some stuff.";exit 1)"
    local exitCode=$?
    echo "output: $output"
    echo "exitCode: $exitCode"
}

function funWithoutLocalVar() {
    output="$(echo "Doing some stuff.";exit 1)"
    local exitCode=$?
    echo "output: $output"
    echo "exitCode: $exitCode"
}

funWithLocalVar
funWithoutLocalVar

これの出力は次のとおりです。

nick.parry@nparry-laptop1:~$ ./tmp.sh 
output: Doing some stuff.
exitCode: 0
output: Doing some stuff.
exitCode: 1

たぶん誰も気にしないが、私はした。明らかにそうではないのに、ステータスコードが常に0だった理由を理解するのに1分かかりました。理由は100%明確ではありません。しかし、これを知っているだけで役に立ちました。

10
Nick P.

昨日(2018年8月29日)同じ問題に遭遇しました。

Nick P.の回答 で言及されたlocalと、 受け入れられた回答 の@sevkoのコメントに加えて、グローバルスコープのdeclareも同じ動作をします。

これが私のBashコードです。

#!/bin/bash

func1()
{
    ls file_not_existed
    local local_ret1=$?
    echo "local_ret1=$local_ret1"

    local local_var2=$(ls file_not_existed)
    local local_ret2=$?
    echo "local_ret2=$local_ret2"

    local local_var3
    local_var3=$(ls file_not_existed)
    local local_ret3=$?
    echo "local_ret3=$local_ret3"
}

func1

ls file_not_existed
global_ret1=$?
echo "global_ret1=$global_ret1"

declare global_var2=$(ls file_not_existed)
global_ret2=$?
echo "global_ret2=$global_ret2"

declare global_var3
global_var3=$(ls file_not_existed)
global_ret3=$?
echo "global_ret3=$global_ret3"

出力:

$ ./declare_local_command_substitution.sh 2>/dev/null 
local_ret1=2
local_ret2=0
local_ret3=2
global_ret1=2
global_ret2=0
global_ret3=2

上記の出力のlocal_ret2およびglobal_ret2の値に注意してください。終了コードは、localおよびdeclareによって上書きされます。

私のBashバージョン:

$ echo $BASH_VERSION 
4.4.19(1)-release
2
Zhi Zhu

(元の質問への回答ではなく、コメントするには長すぎます)

export A=$(false); echo $?は0を出力することに注意してください!どうやら devnull's answer で引用されているルールは適用されなくなりました。その引用に少しコンテキストを追加するには(エンファシスマイニング):

3.7.1単純なコマンド拡張

...

展開後にコマンド名が残っている場合、実行は説明されているように進行しますbelowそれ以外の場合、コマンドは終了します。拡張の1つにコマンド置換が含まれていた場合、コマンドの終了ステータスは、最後に実行されたコマンド置換の終了ステータスです。コマンドの置換がなかった場合、コマンドはステータス0で終了します。

3.7.2コマンドの検索と実行[—これは「以下」のケースです]

IIUCのマニュアルでは、var=foo構文の特殊なケースとしてvar=foo command...について説明しています(かなりわかりにくい!)。 「最後のコマンド置換の終了ステータス」ルールは、コマンドなしの場合にのみ適用されます。

export var=fooを「変更された割り当て構文」と考えるのは魅力的ですが、そうではありません— exportは組み込みコマンドです(たまたま割り当てのような引数を取ります)。

=> varをエクスポートし、コマンド置換ステータスをキャプチャする場合は、2段階で実行します。

A=$(false)
# ... check $?
export A

この方法はset -eモードでも機能します。コマンド置換が0以外を返すとすぐに終了します。