语法错误时不执行“ERR”陷阱
Posted
技术标签:
【中文标题】语法错误时不执行“ERR”陷阱【英文标题】:`ERR` trap is not executed when syntax error 【发布时间】:2020-02-07 13:41:12 【问题描述】:根据man bash
,
set -e
如果 (snip) 立即退出。
ERR
上的陷阱(如果已设置)会在 shell 退出之前执行。
但是,下面的脚本不会调用ERR
陷阱。
trap 'echo ERR; sleep 1' ERR
trap 'echo EXIT; sleep 1' EXIT
set -e
array=(a b c)
echo $(( $#array[@] - 1)) #note the closing bracket is forgotten
echo "after"
预期的结果是
$ bash script.sh
4.sh: line 7: $#array[@] - 1: bad substitution
ERR
EXIT
# and then shell exits
实际结果是
$ bash script.sh
4.sh: line 7: $#array[@] - 1: bad substitution
EXIT
# and then shell exits
如果我删除set -e
这一行,那么
$ bash script2.sh
4.sh: line 7: $#array[@] - 1: bad substitution
after #the last echo command is executed
EXIT
这意味着set -e
捕获了语法错误。为什么在 shell 确实 退出时没有调用 ERR
陷阱?
环境:
我在两台机器上测试了脚本。
$ bash --version
GNU bash, version 4.4.12(1)-release (arm-unknown-linux-gnueabihf)
Copyright (C) 2016 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software; you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
$ bash --version
GNU bash, version 5.0.11(1)-release (x86_64-pc-linux-gnu)
Copyright (C) 2019 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software; you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
补充:
根据发布的答案,陷阱没有执行是很自然的,这是因为echo $(( $#array[@] - 1))
没有完成执行以返回退出状态。我的理解对吗?
但是,man bash
将 set -e
解释为
如果管道(可能由单个简单命令组成)、列表或复合命令(参见上面的 SHELL GRAMMAR)以非零状态退出,则立即退出。
我认为这也需要命令来完成。如果echo $(( $#array[@] - 1))
没有完成它的执行,我相信set -e
在这里不应该工作,echo "after"
应该被执行。
补充2:
根据oguz ismail's comment,这是一个文档问题。 POSIX 说set -e
应该在什么时候退出shell
返回非零退出状态(如您在man bash
中所见)
或 shell错误发生
和shell error includes syntax error。但是man bash
缺少对第二种情况的解释,对吧?如果是这样,除了man bash
没有准确解释实现以及声明“这些是errexit
(-e
) 选项遵循的相同条件”这一事实之外,一切都是一致的。在trap
man bash
的解释中找到。
【问题讨论】:
我相信这个问题是这里的主题,但是您可能还想在Unix&Linux SE 上发帖,您可能有更多机会快速得到答案。 @Aaron 谢谢你的建议。如果没有好的答案,我会考虑。 您似乎错过了ERR
测试返回代码来自 已完成 命令的点,而不是来自尚未执行的命令(怎么可能?)。您所观察到的是正确和预期的行为。
@cdarke 好的,我明白了。但是为什么set -e
会捕获语法错误呢? man bash
并没有说它捕获语法错误。 (POSIX 这么说(参见 OP 中的 Suppliment2),但 bash
不符合 POSIX。)
查看源代码,set -e
(errexit
) 和traps ERR
使用不同的代码和机制来测试和处理错误。 set -e
立即退出,ERR
陷阱仅在命令运行时进行测试,这就是 set -e
捕获语法错误而 ERR
陷阱没有的原因。 man bash
暗示这两种机制的作用方式相同,但显然它们没有而且它们从来没有。
【参考方案1】:
这是因为多年来set -e
在bash
中的实现方式很奇怪。它涉及许多特定情况,预计在这些情况下会起作用。
来自man bash
:
trap ... ERR
如果 sigspec 是 ERR,则只要管道 (可能由单个简单命令组成)、a列表或复合命令返回非零退出状态,受以下条件限制:
如果失败的命令是紧跟在while
或until
关键字之后的命令列表的一部分,则不会执行ERR 陷阱...
...if
语句中的部分测试...
...在&&
或||
列表中执行的命令的一部分,除了最后一个&&
或||
之后的命令...
...管道中的任何命令,但最后一个...
...或者如果使用!
反转命令的返回值。
这些与 errexit -e
选项所遵循的条件相同。
您在此示例代码中拥有的是shell expansion error,上面提到的子句从不真正适用。在上述所有子句中,一个不正确的命令运行/完成,并且设置一个返回码返回给你的 ERR
陷阱以触发。
但是当遇到echo $(( $#array[@] - 1))
行时,在算术展开失败后不久,没有实际运行的命令会触发ERR
陷阱,因为要触发陷阱,该命令需要完成。来自man bash
页面
Bash
正在等待命令完成并接收到已设置陷阱的信号,则在命令完成之前不会执行陷阱。
【讨论】:
实际上ERR
陷阱最初是在 korn shell 中实现的,而 bash 只是模拟了这种行为。
请参阅我在 OP 中的补充。【参考方案2】:
ERR
陷阱从未捕获语法错误,并且不是为此而设计的。来自man bash
:
If a sigspec is ERR, the command arg is executed whenever a
pipeline (which may consist of a single simple command), a list,
or a compound command returns a non-zero exit status ...
在这种情况下,该命令永远不会执行,因此不会返回非零状态,脚本在此之前失败。
【讨论】:
man bash
说If a sigspec is ERR, the command ard is executed whenever ... These are the same conditions obeyed by the errexit (-e) option.
在我的例子中,set -e
捕获了语法错误,所以如果trap <command> ERR
在与set -e
相同的条件下工作,则应该执行ERR
陷阱,我相信。
@ynn 但它在与set -e
相同的条件下不起作用,这就是重点! ERR
陷阱在命令执行后 被调用,在这种情况下命令永远不会执行。
但是man bash
在set -e
命令部分也说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.
所以如果你的解释是正确的,shell 不应该退出(应该执行echo "after"
)。但是,就我而言,shell 出于某种原因退出。
@ynn:您缺少手册页中trap
的摘录。在命令完成之前不会触发陷阱,但语法错误的行永远不会完成
@ynn 标准说 -e 当此选项打开时,当任何命令失败时(由于第 2.8.1 节中列出的任何原因,Shell 错误的后果或返回退出status 大于零),shell 将立即退出,所以这只是一个文档问题以上是关于语法错误时不执行“ERR”陷阱的主要内容,如果未能解决你的问题,请参考以下文章