变量赋值的返回状态是如何确定的?

变量赋值的返回状态是如何确定的?

我在脚本中看到过这样的构造:

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
是退出状态。赋值后发生的赋值不会将整体退出状态设置为 0。cmd4E=D=

伊卡洛斯, 在他对phk问题的回答,提出了一个重要的观点:变量可以设置为只读。倒数第三段POSIX 标准第 2.9.1 节说,

如果任何变量赋值尝试将值分配给该变量只读在当前 shell 环境中设置属性(无论是否在该环境中赋值),都会发生变量赋值错误。看Shell 错误的后果对于这些错误的后果。

所以如果你说

readonly A
C=Garfield A=Felix T=Tigger

Garfield返回状态为 1。字符串、Felix和/或是否Tigger 被命令替换替换并不重要- 但请参阅下面的注释。

2.8.1 Shell 错误的后果还有另一堆文本和一个表格,并以

在表中显示的所有要求交互式 shell 不退出的情况下,shell 不应对发生错误的命令执行任何进一步的处理。

有些细节是有道理的;有些则不然:

  • A=有时会做作业中止命令行,正如最后一句话似乎指定的那样。在上面的示例中,C被设置为Garfield,但未T设置(当然,两者都没有设置 A)。
  • 同样, 执行 但不执行。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) T=$(命令3命令0
执行 和,但不执行。 (请注意,这与没有命令时的行为相反。)并且它在 的环境中设置(以及) 。我想知道这是否是 bash 中的一个错误。cmd1cmd3cmd2TCcmd0


没那么简单:

这个答案的第一段指的是“简单命令”。  规格说,

“简单命令”是一系列可选的变量赋值和重定向,可以任意顺序,可选地后跟单词和重定向,并由控制运算符终止。

这些语句类似于我的第一个示例块中的语句:

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

其中前三个包括变量赋值,后三个包括命令替换。

但有些变量赋值并不那么简单。  重击(1)说,

赋值语句也可以作为参数出现alias,declare,typeset,export,readonly, 和local内置命令(宣言命令)。

为了exportPOSIX 规范说,

退出状态

    0
      所有名称操作数均已成功导出。
    >0
      至少有一个名称无法导出,或者-p指定了该选项并发生了错误。

POSIX 不支持local,但是重击(1)说,

使用时出现错误local当不在函数内时。返回状态为 0,除非local在函数外部使用,无效姓名提供,或姓名是一个只读变量。

阅读字里行间,我们可以看到声明命令如下

export FOO=$(bar)

local FOO=$(bar)

更像是

foo $(bar)

只要它们忽略退出状态bar 并根据主命令(exportlocalfoo)为您提供退出状态。所以我们有像这样的奇怪现象

   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' bash它在 Bash ( )中有记录:

如果展开后还剩下命令名....否则,命令退出。 ...如果没有命令替换,命令将以零状态退出。

换句话说(我的话):

如果扩展后没有留下命令名,并且没有执行命令替换,则命令行将以零状态退出。

相关内容