linux Bash中变量赋值到命令替换的退出代码

klr1opcd  于 2023-06-05  发布在  Linux
关注(0)|答案(5)|浏览(580)

我很困惑,当简单地执行变量赋值并使用命令替换时,命令将返回什么错误代码:

a=$(false); echo $?

它输出1,这让我认为变量赋值不会在最后一个错误代码后清除或产生新的错误代码。但当我试着这样做时:

false; a=""; echo $?

它输出0,显然这是a=""返回的,它覆盖了false返回的1
我想知道为什么会发生这种情况,变量赋值是否有任何特殊性,与其他正常命令不同?或者仅仅是因为a=$(false)被认为是一个单一的命令,只有命令替换部分有意义?

--更新--

谢谢大家,从回答和评论中我得到了一个观点“当你使用命令替换分配一个变量时,退出状态是命令的状态。”(作者@Barmar),这个解释非常清晰易懂,但对程序员来说不够精确,我想从TLDP或GNU手册页等权威机构看到这一点的参考,请帮助我找到它,再次感谢!

o2gm4chl

o2gm4chl1#

正如其他人所说,命令替换的退出代码就是被替换命令的退出代码,所以

FOO=$(false)
echo $?
---
1

然而,出乎意料的是,将export添加到的开头会产生不同的结果:

export FOO=$(false)
echo $?
---
0

这是因为,当替代命令false失败时,export命令成功,这是语句返回的退出代码。

v09wglhw

v09wglhw2#

在执行命令时,$(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
编辑:
引自手册:
如果其中一个扩展包含命令替换,则命令的退出状态是执行的最后一个命令替换的退出状态。
引用自BASHFAQ/002

如何在变量中存储命令的返回值和/或输出?

...
output=$(command)
status=$?
分配给outputcommand的退出状态没有影响,它仍然在$?中。
这不是bash特有的。引用The Open Group Base Specifications Issue 7,POSIX.1-2017的“Shell & Utilities”卷中第2.9.1节“简单命令”的结尾:
如果没有命令名称,但命令包含命令替换,则该命令应完成,并具有上次执行的命令替换的退出状态

0s0u357o

0s0u357o3#

请注意,当与local结合使用时(如local *variable*="$(*command*)"),情况并非如此。即使*command*失败,该表单也将成功退出。
以Bash脚本为例:

#!/bin/bash

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

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

funWithLocalAndAssignmentTogether
funWithLocalAndAssignmentSeparate

下面是它的输出:

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

这是因为local实际上是一个内置命令,像local *variable*="$(*command*)"这样的命令在 * 替换*command*的输出后调用local *。因此,您可以从local获取退出状态。

anauzrmj

anauzrmj4#

我昨天(2018年8月29日)遇到了同样的问题。
除了在Nick P.'s answer中提到的local和@sevko在accepted answer中的评论外,全局范围内的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_ret2global_ret2的值。退出代码被localdeclare覆盖。
我的Bash版本:

$ echo $BASH_VERSION 
4.4.19(1)-release
6jygbczu

6jygbczu5#

(not对原始问题的答复,但太长,无法评论)
注意,export A=$(false); echo $?输出0!显然,devnull's answer中引用的规则不再适用。为这句话添加一点上下文(强调我的):
3.7.1简单的命令扩展
...
如果在展开后还有命令名,则执行将按 * 如下所述 * 进行。否则,命令退出。如果其中一个扩展包含命令替换,则命令的退出状态是执行的最后一个命令替换的退出状态。如果没有命令替换,则命令以零状态退出。
3.7.2命令搜索和执行[ - this is the“below”case]
IIUC手册将var=foo描述为var=foo command...语法的特殊情况(非常混乱!“最后命令替换的退出状态”规则仅适用于无命令的情况。
虽然很容易将export var=foo视为“修改的赋值语法”,但事实并非如此-export是一个内置命令(只是碰巧接受类似赋值的参数)。
=>如果要导出var AND capture命令替换状态,请分两个阶段执行:

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

这种方式也适用于set -e模式-如果命令替换返回非0,则立即退出。

相关问题