【问题标题】:Exit code of variable assignment to command substitution in Bash变量赋值的退出代码以在 Bash 中替换命令
【发布时间】:2013-12-08 02:31:32
【问题描述】:

我对在执行变量赋值和命令替换时该命令将返回什么错误代码感到困惑:

a=$(false); echo $?

它输出1,这让我认为变量赋值不会扫描或在最后一个错误代码上产生新的错误代码。但是当我尝试这个时:

false; a=""; echo $?

它输出0,显然这是a=""返回的内容,它覆盖了false返回的1

我想知道为什么会发生这种情况,变量赋值有什么特殊性与其他普通命令不同吗?或者只是因为a=$(false) 被认为是单个命令,只有命令替换部分才有意义?

-- 更新--

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

【问题讨论】:

标签: linux bash shell exit-code


【解决方案1】:

$(command) 执行命令时,允许output of the command to replace itself

当你说:

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:

如果其中一个扩展包含命令替换,则退出 命令的状态是最后一个命令的退出状态 执行替换。

引用BASHFAQ/002:

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

...

output=$(command)

status=$?

分配给outputcommand 的退出状态没有影响,这 还在$?

【讨论】:

  • 请记住,这不适用于local 变量(例如,local output=$(command)),因为local 本身就是一个命令,它的退出代码将覆盖指定函数的退出代码.
  • @sevko 这个区别让一切变得不同,感谢您指出这一点!
  • 关于@sevko & @qodeninja 的关于locals 的cmets,除非变量也是readonlylocal my_var; my_var=$(command); local status=$?
  • 天哪,这浪费了我很多时间。谢谢@sevko,我自己可能从来没有想过这个。
  • local 声明与命令替换相结合的缺陷在Bash Pitfall #27 中进行了描述。它还会影响declareexport
【解决方案2】:

请注意,与local 结合时,情况并非如此,如local <em>variable</em>="$(<em>command</em>)"。即使<em>command</em> 失败,该表单也会成功退出。

以这个 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 <em>variable</em>="$(<em>command</em>)" 这样的命令调用local 之后 替换<em>command</em> 的输出。所以你从local 获得退出状态。

【讨论】:

  • 原因是localexport 也是如此)是一个 bash 内置函数,它在第一个示例中调用的方式导致成功,因此是 0 退出代码。
  • 希望您不要介意,我已编辑此答案以澄清情况;仅当您将 local 与单个语句中的赋值组合时才会出现此问题。 (有趣的事实:因为local 是一个内置命令,而不是特殊语法,你甚至可以编写类似local "$(echo foo=bar)" 的东西,它的行为与local foo=bar 相同,创建一个名为foo 的局部变量并将其初始化为bar。相比之下,命令"$(echo foo=bar)" 将给你bash: foo=bar: command not found,因为此时 Bash 并不期待分配。)
【解决方案3】:

我昨天(2018 年 8 月 29 日)遇到了同样的问题。

除了Nick P.'s answer中提到的localaccepted answer中@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_ret2global_ret2 的值。退出代码被localdeclare 覆盖。

我的 Bash 版本:

$ echo $BASH_VERSION 
4.4.19(1)-release

【讨论】:

    【解决方案4】:

    (不是原始问题的答案,但评论太长了)

    请注意,export A=$(false); echo $? 输出 0!显然devnull's answer 中引用的规则不再适用。为该引用添加一些上下文(强调我的):

    3.7.1 简单命令扩展

    ...

    如果展开后还有命令名,则执行如下所述否则,命令退出。如果其中一个扩展包含命令替换,则命令的退出状态是最后执行的命令替换的退出状态。如果没有命令替换,则命令以零状态退出。

    3.7.2 命令搜索和执行 [ — 这是“下面”的情况]

    IIUC 手册将var=foo 描述为var=foo command... 语法的特例(相当混乱!)。 “最后一个命令替换的退出状态”规则仅适用于无命令情况。

    虽然很容易将export var=foo 视为“修改后的赋值语法”,但事实并非如此——export 是一个内置命令(恰好采用类似赋值的参数)。

    => 如果您想导出 var 并捕获命令替换状态,请分 2 个阶段进行:

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

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

    【讨论】:

      猜你喜欢
      • 2012-03-20
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-12-29
      • 2011-01-17
      相关资源
      最近更新 更多