set -e
当使用( errexit
)、set -u
( nounset
) 以及 ERR 和 EXIT 陷阱时,我观察到一些奇怪的行为。它们看起来是相关的,因此将它们放在一个问题中似乎是合理的。
1)set -u
不触发ERR陷阱
代码:
#!/bin/bash trap 'echo "ERR (rc: $?)"' ERR set -u echo ${UNSET_VAR}
- 预期:ERR 陷阱被调用,RC != 0
- 实际:ERR 陷阱是不是被叫,RC == 1
- 注意:
set -e
不改变结果
2)set -eu
在 EXIT 陷阱中使用退出代码是 0 而不是 1
代码:
#!/bin/bash trap 'echo "EXIT (rc: $?)"' EXIT set -eu echo ${UNSET_VAR}
- 预期:EXIT 陷阱被调用,RC == 1
- 实际:调用 EXIT 陷阱,RC == 0
- 注意:使用 时
set +e
,RC == 1。当任何其他命令引发错误时,EXIT 陷阱将返回正确的 RC。 - 编辑:有一篇关于这个主题的帖子有趣的评论表明这可能与正在使用的 Bash 版本有关。使用 Bash 4.3.11 测试此代码片段会得到 RC=1,因此效果更好。不幸的是,目前不可能在所有主机上升级 Bash(从 3.2.51 开始),因此我们必须想出其他解决方案。
任何人都可以解释这些行为吗?
搜索这些主题并不是很成功,考虑到有关 Bash 设置和陷阱的帖子数量,这相当令人惊讶。有一个论坛主题虽如此,但结论却并不令人满意。
答案1
从man bash
:
set -u
- 执行参数扩展时,将未设置的变量和特殊参数以外的参数
"@"
视为"*"
错误。如果尝试对未设置的变量或参数进行扩展,shell 会打印一条错误消息,并且如果不是-i
交互式的,则会以非零状态退出。
- 执行参数扩展时,将未设置的变量和特殊参数以外的参数
POSIX 规定,如果发生膨胀误差,非交互式 shell应退出当扩展与 shell 特殊内置相关联时(无论如何,这是一个经常被忽略的区别bash
,所以也许是无关紧要的)或除此之外的任何其他实用程序。
- Shell 错误的后果:
- 一个膨胀误差是当 shell 扩展定义在单词扩展进行(例如,
"${x!y}"
, 因为!
不是有效的运算符);一个实现可能如果能够在标记化过程中而不是在扩展过程中检测到它们,则将它们视为语法错误。 - [A]n 交互式 shell 应将诊断消息写入标准错误而不退出。
- 一个膨胀误差是当 shell 扩展定义在单词扩展进行(例如,
也来自man bash
:
trap ... ERR
- 如果 sigspec 是呃, 命令精氨酸每当管道时执行(可能由一个简单的命令组成)、列表或复合命令返回非零退出状态,但满足以下条件:
- 这呃如果失败的命令是紧跟在
while
oruntil
关键字之后的命令列表的一部分,则不会执行 trap... - ...声明中测试的一部分
if
... &&
...在或列表中执行的命令的一部分,除了最后的或||
之后的命令...&&
||
- ...管道中除最后一个命令之外的任何命令...
- ...或者如果使用 来反转命令的返回值
!
。
- 这呃如果失败的命令是紧跟在
- 这些条件与错误退出
-e
选项。
- 如果 sigspec 是呃, 命令精氨酸每当管道时执行(可能由一个简单的命令组成)、列表或复合命令返回非零退出状态,但满足以下条件:
请注意上面的呃陷阱就是对某些人的评价其他命令的返回。但是当一个膨胀误差发生时,没有运行命令来返回任何内容。在你的例子中,echo
永远不会发生- 因为当 shell 计算并扩展其参数时,它会遇到一个-u
nset 变量,该变量已由显式 shell 选项指定,以导致立即退出当前的脚本化 shell。
所以出口trap(如果有)将被执行,并且 shell 会退出并显示一条诊断消息,并且退出状态不是 0 - 正如它应该做的那样。
至于控制中心:0事情,我希望这是某种版本特定的错误 - 可能与两个触发器有关出口同时发生并且一方获得另一方的退出代码(这不应该发生)。无论如何,使用bash
安装的最新二进制文件pacman
:
bash <<\IN
printf "shell options:\t$-\n"
trap 'echo "EXIT (rc: $?)"' EXIT
set -eu
echo ${UNSET_VAR}
IN
我添加了第一行,这样您就可以看到 shell 的条件是脚本化 shell 的条件 - 它是不是交互的。输出是:
shell options: hB
bash: line 4: UNSET_VAR: unbound variable
EXIT (rc: 1)
以下是一些相关注释最近的变更日志:
- 修复了导致异步命令设置不正确的错误
$?
。 - 修复了导致生成错误消息的错误扩展错误命令中的
for
行号错误。 - 修复了导致信号情报和信号退出不能
trap
用于异步子 shell 命令。 - 修复了导致第二个及后续事件的中断处理问题信号情报被交互式 shell 忽略。
- shell 在运行这些信号的处理程序时不再阻止信号的接收
trap
,并允许最多trap
递归运行处理程序 (在处理程序执行trap
时运行处理程序)trap
。
我认为最相关的是最后一个或第一个,或者可能是两者的组合。 Atrap
处理程序就其本质而言异步因为它的全部工作就是等待和处理异步信号。您可以使用 和 同时触发-eu
两个$UNSET_VAR
。
所以也许你应该更新,但如果你喜欢自己,你会完全使用不同的 shell 来完成它。
答案2
(我使用的是 bash 4.2.53)。对于第 1 部分,bash 手册页只是说“错误消息将写入标准错误,并且非交互式 shell 将退出”。它并没有说将调用 ERR 陷阱,尽管我同意如果这样做的话它会很有用。
务实地说,如果您真正想要的是更干净地处理未定义的变量,一个可能的解决方案是将大部分代码放在一个函数中,然后在子 shell 中执行该函数并恢复返回代码和 stderr 输出。这是一个示例,其中“cmd()”是函数:
#!/bin/bash
trap 'rc=$?; echo "ERR at line ${LINENO} (rc: $rc)"; exit $rc' ERR
trap 'rc=$?; echo "EXIT (rc: $rc)"; exit $rc' EXIT
set -u
set -E # export trap to functions
cmd(){
echo "args=$*"
echo ${UNSET_VAR}
echo hello
}
oops(){
rc=$?
echo "$@"
return $rc # provoke ERR trap
}
exec 3>&1 # copy stdin to use in $()
if output=$(cmd "$@" 2>&1 >&3) # collect stderr, not stdout
then echo ok
else oops "fail: $output"
fi
在我的狂欢上我得到
./script my stuff; echo "exit was $?"
args=my stuff
fail: ./script: line 9: UNSET_VAR: unbound variable
ERR at line 15 (rc: 1)
EXIT (rc: 1)
exit was 1
答案3
对于那些在寻找如何使用nounset
with时发现这一点的人trap ERR
。
以下不会触发错误陷阱:
set -o nounset
trap 'trapped error on line $LINENO' ERR
echo $foo
预期产出
trapped error on line 5
实际产量
./script: line 5: foo: unbound variable
如果您想保持该nounset
标志处于启用状态,一种解决方法是检查退出陷阱上的返回代码。
set -o nounset
trap '[[ $? != 0 ]] && echo caught error' EXIT
echo $foo
输出
./script: line 5: foo: unbound variable
caught error