我在脚本中看到过这样的构造:
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。
cmd4
E=
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)
cmd1
cmd3
但,在我的 bash 版本(包括 4.1.X 和 4.3.X)中,它做执行。 (顺便说一句,这进一步弹劾了 phk 的解释,即赋值的退出值适用于赋值的右侧之前。)cmd2
但这里有一个惊喜:
在我的 bash 版本中,
只读A C=某物A=某物时间=某物 命令0
做执行。尤其,cmd0
C=$(命令1) A=$(命令2) T=$(命令3) 命令0执行 和,但不执行。 (请注意,这与没有命令时的行为相反。)并且它在 的环境中设置(以及) 。我想知道这是否是 bash 中的一个错误。
cmd1
cmd3
cmd2
T
C
cmd0
没那么简单:
这个答案的第一段指的是“简单命令”。 规格说,
“简单命令”是一系列可选的变量赋值和重定向,可以任意顺序,可选地后跟单词和重定向,并由控制运算符终止。
这些语句类似于我的第一个示例块中的语句:
$ FOO=BAR
$ FOO=$(bar)
$ FOO=$(bar) baz
$ foo $(bar)
其中前三个包括变量赋值,后三个包括命令替换。
但有些变量赋值并不那么简单。 重击(1)说,
赋值语句也可以作为参数出现
alias
,declare
,typeset
,export
,readonly
, 和local
内置命令(宣言命令)。
为了export
,POSIX 规范说,
退出状态
0
所有名称操作数均已成功导出。
>0至少有一个名称无法导出,或者
-p
指定了该选项并发生了错误。
POSIX 不支持local
,但是重击(1)说,
使用时出现错误
local
当不在函数内时。返回状态为 0,除非local
在函数外部使用,无效姓名提供,或姓名是一个只读变量。
阅读字里行间,我们可以看到声明命令如下
export FOO=$(bar)
和
local FOO=$(bar)
更像是
foo $(bar)
只要它们忽略退出状态bar
并根据主命令(export
、local
或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
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 ( )中有记录:
如果展开后还剩下命令名....否则,命令退出。 ...如果没有命令替换,命令将以零状态退出。
换句话说(我的话):
如果扩展后没有留下命令名,并且没有执行命令替换,则命令行将以零状态退出。