Bash 和 Dash 使用 `errexit` 不一致地检查命令替换错误代码

Bash and Dash inconsistently check command substitution error codes with `errexit`

我似乎在 dashbash 使用 errexit 选项检查错误条件的方式中遇到了非常非常奇怪的不一致。

同时使用 dashbash 不带 set -e/set -o errexit 选项,以下程序:

foo()
{
    echo pre
    bar=$(fail)
    echo post
}

foo

将打印以下内容(dash 的错误字符串略有不同):

pre
./foo.sh: line 4: fail: command not found
post

使用 errexit 选项,它将打印以下内容:

pre
./foo.sh: line 4: fail: command not found

但是,令人惊讶的是,如果 barlocal,程序将始终 echo prepost。更具体地说,同时使用 dashbash 以及不带 errexit 选项,以下程序:

foo()
{
    echo pre
    local bar=$(fail)
    echo post
}

foo

将打印以下内容:

pre
./foo.sh: line 4: fail: command not found
post

换句话说,分配给局部变量的命令替换的 return 值似乎未被 errexit 检查,但如果变量是全局变量。

如果这两个 shell 都没有发生,我会倾向于认为这只是一个角落案例错误。由于 dash 专门设计为 POSIX 符合我想知道这种行为是否实际上是由 POSIX 标准指定的,尽管我很难想象这有什么意义。

dash(1)errexit 有这样的说法:

If not interactive, exit immediately if any untested command fails. The exit status of a command is considered to be explicitly tested if the command is used to control an if, elif, while, or until; or if the command is the left hand operand of an “&&” or “||” operator.

bash(1) 有点冗长,但我很难理解它:

Exit immediately if a pipeline (which may consist of a single simple command), a list, or a compound command (see SHELL GRAMMAR above), exits with a non-zero status. The shell does not exit if the command that fails is part of the command list immediately following a while or until keyword, part of the test following the if or elif reserved words, part of any command executed in a && or || list except the command following the final && or ||, any command in a pipeline but the last, or if the command's return value is being inverted with !. If a compound command other than a subshell returns a non-zero status because a command failed while -e was being ignored, the shell does not exit. A trap on ERR, if set, is executed before the shell exits. This option applies to the shell environment and each subshell environment separately (see COMMAND EXECUTION ENVIRONMENT above), and may cause subshells to exit before executing all the commands in the subshell.

If a compound command or shell function executes in a context where -e is being ignored, none of the commands executed within the compound command or function body will be affected by the -e setting, even if -e is set and a command returns a failure status. If a compound command or shell function sets -e while executing in a context where -e is ignored, that setting will not have any effect until the compound command or the command containing the function call completes.

TL;DR local 的退出状态 "hides" 出现在其参数之一中的任何命令替换的退出状态。


变量赋值的退出状态记录很少(或者至少,我在快速浏览各种手册页和 POSIX 规范时找不到任何细节)。据我所知,退出状态被视为发生在赋值值中的 last 命令替换的退出状态,如果没有命令替换则为 0。非最终命令替换似乎包含在 "tested" 情况列表中,如

这样的赋值
foo=$(false)$(true)

不退出并设置 errexit

然而,

local 是一个命令本身,其退出状态通常为 0,独立于其参数中出现的任何命令替换。也就是说,虽然

foo=$(false)

退出状态为 1,

local foo=$(false)

将具有 0 的退出状态,参数中的任何命令替换似乎被视为 "tested" 用于 errexit.

的目的

试试这个:

#!/bin/bash
set -e

foo()
{
    echo pre
    local bar
    bar=$(fail)
    echo post
}

foo

exit

!!或者 !!

#!/bin/bash

foo()
{
    set -e
    echo pre
    local bar
    bar=$(fail)
    echo post
}

foo

exit

输出:

$ ./errexit_function 
pre
./errexit_function: line 8: fail: command not found
$ echo $?
127

据我所知,这是解决 bash 中的错误的方法,但试试这个,

#!/bin/bash
set -e

foo()
{
    echo true || return_value=$?
    echo the command returned a value of ${return_value:-0}
    $(fail) || return_value=$?
    echo the command returned a value of ${return_value:-0}
    echo post
}

foo

exit