为什么具有非零退出状态的命令会发送 ERR 信号,即使它是“&& 或 || 的一部分”列表”?

为什么具有非零退出状态的命令会发送 ERR 信号,即使它是“&& 或 || 的一部分”列表”?

man bash包括此文档以供使用trap

trap [-lp] [[arg] sigspec ...]
    The ERR trap is not executed if the failed command is part of the command list
    immediately following a while or until keyword, part of the test in an if statement,
    part of a && or || list, or if the command's return value is being inverted via !.
    These are the same conditions obeyed by the errexit option.

我明白那个“&& 或 || 的一部分列表”意味着如果命令是具有这些控制运算符的列表的一部分,即使它具有非零退出状态,它不应发送 ERR 信号(或者如果使用则退出脚本set -o errexit)。

然而,这里有一个测试脚本似乎与此相矛盾:

#!/usr/bin/env bash

_trap_err() {
    local status=$? sig=$1 line=$2;
    echo "Exit status ${status} on line ${line}: \`${BASH_COMMAND}\`";
}
trap '_trap_err ERR $LINENO' ERR;

function control_operators() {
    # The next line will send an ERR signal.
    [[ 1 -eq 2 ]] && echo Hello;
}

control_operators;

# The next line will not send an ERR signal.
[[ 1 -eq 2 ]] && echo Hello;

echo Done;

输出是:

❯ test.sh
Exit status 1 on line 14: `[[ 1 -eq 2 ]]`
Done

因为[[ 1 -eq 2 ]]是“&& 或 || 的一部分”列出”它不应触发 ERR 信号

预期输出是:

❯ test.sh
Done

此外,仅列出清单[[ 1 -eq 2 ]] && echo Hello 里面control_operators函数发送 ERR 信号,但[[ 1 -eq 2 ]] && echo Hello 外部该函数没有。

该脚本环境中设置的选项是(输出set -o | grep 'on$'):

braceexpand     on
hashall         on
interactive-comments    on
xtrace          on

问题:

  1. 这种行为是否正常,或者我是否在列表中错误地使用了控制运算符(或错误地解释了文档)?
  2. 当命令是条件表达式的一部分并且实际上并不构成错误状态时,避免触发 ERR 陷阱的最佳方法是什么?
    • 一种选择是使用控制运算符添加|| true到每个列表的末尾。
    • 另一种选择是使用速记if [[ 1 -eq 2 ]]; then echo Hello; fi代替&&;这不会导致发送 ERR 信号,因为(根据man bash)“if 语句中测试的一部分”——尽管我很困惑为什么当“&& 或 || 的一部分”时这会起作用。列表”没有。
  3. 为什么[[ 1 -eq 2 ]]只有在函数内部时才发送 ERR 信号?

更新:@Kusalananda 的第一个答案对于上面的示例是正确的(添加到函数return 0control_operators可以防止 ERR 信号)。然而,这是另一个例子,它似乎违反了“&& 或 || 的一部分”列表”规则:

#!/usr/bin/env bash
_trap_err() {
    local status=$? sig=$1 line=$2;
    echo "Exit status ${status} on line ${line}: \`${BASH_COMMAND}\`";
}
trap '_trap_err ERR $LINENO' ERR;
true && false;
echo Done;

该脚本的输出是:

❯ test.sh
Exit status 1 on line 7: `false`
Done

该行true && false不应发送 ERR 信号,因为该命令false是“&& 或 || 的一部分”列表”。那么,为什么这么做呢?

更新2:我正在使用 bash 3.2 在 macOS 上工作,这就是上面的代码片段man bash没有提及的原因“除了最后的 && 或 || 后面的命令”

答案1

ERR由于调用的返回状态control_operators非零,您的脚本正在触发陷阱。函数中的测试没有直接地触发陷阱。

陷阱输出通过打印的行号来指示这一点,该行号将是调用该函数的行。

陷阱输出还指示这个非零退出状态来自哪里,这是函数中测试的结果。由于测试是函数中执行的最后一件事,因此这设置了函数的退出状态。

该函数调用不是 AND 或 OR 列表的一部分,因此会触发陷阱。


对于您更新的问题,您忘记了ERR如果返回非零退出状态的命令是,陷阱仍然会被调用最后的AND 或 OR 列表中的命令。

bash手册中,我强调:

ERR如果失败的命令是紧随whileoruntil关键字的命令列表的一部分、语句中测试的一部分、或列表if中执行的命令的一部分,则不会执行陷阱&&||&&除了最后一个或之后的命令||、管道中除最后一个命令之外的任何命令,或者如果使用 反转命令的返回值 !

您的示例是true && false,并且由于是falseAND 列表中的最后一个,因此它会触发ERR陷阱。

另一种说法是,ERR如果命令决定退出状态列表或管道的返回非零退出状态(如果!与该命令一起使用,则返回零退出状态)。

相关内容