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


10

我已经看到了如下脚本中的构造:

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

这在某处有记录吗?如何确定变量的返回状态?它与命令替换有何关系?(例如,我会得到相同的结果if echo "$(somecommand 2>/dev/null)"; then吗?)

Answers:


12

(在POSIX中)在 Open Group基本规范的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)不计算在内;您将从中获得退出状态foo。释义phk的符号,这里的行为是

    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=

icarus回答phk问题时提出了一个重要观点:变量可以设置为只读。POSIX标准第2.9.1节的倒数第二段说:

如果任何变量赋值试图给在当前shell环境中设置了只读属性的变量赋值(无论是否在该环境中进行赋值),都会发生变量赋值错误。有关这些错误的后果,请参阅Shell错误的后果。

所以如果你说

readonly A
C=Garfield A=Felix T=Tigger

返回状态为1。是否将字符串GarfieldFelix和/或Tigger 替换为命令替换无关紧要,但是请参阅下面的说明。

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

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

一些细节是有道理的;有些不:

  • 正如最后一句话似乎指定的那样,该A=分配有时会中止命令行。在上面的示例中,C将设置为Garfield,但未T设置(当然也不设置  A)。
  • 同样, 执行 但不执行。但是,在我的bash版本(包括4.1.X和4.3.X)中,它确实执行。(顺便说一句,这进一步暗示了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
执行 和,但不会。(请注意,这与没有命令时的行为相反。)并且它在的环境中进行设置(以及)。我不知道这是否是bash中的错误。cmd1cmd3cmd2TCcmd0


没那么简单:

该答案的第一段涉及“简单命令”。  规范说,

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

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

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

前三个变量包括变量分配,后三个变量包括命令替换。

但是某些变量分配并不是那么简单。  bash(1)说,

赋值语句也可能出现作为参数传递给aliasdeclaretypesetexportreadonly,和local内置命令(声明命令)。

对于exportPOSIX规范说,

退出状态

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

POSIX不支持local,但是bash(1)表示,

local当不在函数内时使用是错误的。除非local在函数外部使用,返回的名称无效或name是只读变量,否则返回状态为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)
通配符

1
对于第二个示例(仅FOO=$(bar)),值得注意的是,理论上退出状态分配都可能起作用,请参见unix.stackexchange.com/a/341013/117599
phk

1
@Wildcard:很高兴您喜欢它。我刚刚又更新了一次;您刚刚阅读的版本的很大一部分是错误的。只要你在这里,你会怎么想?这是bash中的bug,即使是只读的,A=foo cmdcmd也会运行A吗?
G-Man说'Resstate Monica''1

1
@phk:(1)有趣的理论,但是我不确定它的意义。再看看我的“惊喜”标题之前的示例。如果A为只读,则命令C=value₁ A=value₂ T=value₃设置C但未设置T(当然A也未设置)-外壳程序终止处理命令行,忽略T=value₃,因为这A=value₂是错误。(2)感谢您对“ 堆栈溢出”问题的链接-我已经发布了有关它的评论。
G-Man说'Resstate Monica''02 /

1
@Wildcard“关于奖励积分,您知道当地人对此有何联系吗?”。是的... local=$(false)有出口值0,因为(从该名男子页): It 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.。对于以这种方式设计的天才来说,这还不够。
David Tonhofer,

2

它以Bash(LESS=+/'^SIMPLE COMMAND EXPANSION' bash)记录:

扩展后是否还有命令名...。否则,命令退出。...如果没有命令替换,则命令以零状态退出。

换句话说(我的话):

如果扩展后没有剩余命令名称,并且没有执行任何命令替换,则退出命令行,状态为零。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.