使用`set -eu`时正确的EXIT和ERR陷阱行为


27

与ERR和EXIT陷阱一起使用set -eerrexit),set -unounset)时,我观察到一些奇怪的行为。它们似乎相关,因此将它们放在一个问题中似乎是合理的。

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。
  • 编辑:关于该主题的一篇SO帖子带有一个有趣的注释,表明这可能与所使用的Bash版本有关。使用Bash 4.3.11测试此代码段会导致RC = 1,所以更好。不幸的是,目前无法在所有主机上升级Bash(从3.2.51开始),因此我们必须提出其他解决方案。

谁能解释这些行为中的任何一个?

搜索这些主题不是很成功,考虑到有关Bash设置和陷阱的帖子数量,这令人惊讶。虽然有一个论坛主题,但是结论并不令人满意。


3
从4开始,我认为bash违反了标准,开始在子外壳中放置陷阱。该陷阱应该与返回时在相同的环境中执行,但是bash已经有一段时间没有这样做了。
mikeserv

1
等一下-您需要解决方案还是解释?如果您想要一个解决方案,那么到底是什么解决方案?您想发生什么?set -eset -u两者都是专门设计来杀死脚本化的shell。在可能触发其应用程序的条件下使用它们将杀死脚本化的Shell。除了使用它们,而是在代码序列中应用这些条件时进行测试,这无可避免。因此,基本上,您可以编写良好的shell代码,也可以使用set -eu
mikeserv

2
实际上,我正在寻找两者,因为我找不到足够的信息说明为什么-u不触发ERR陷阱(这是一个错误,因此不应该触发陷阱)或错误代码为0而不是1。后者似乎是一个错误,已经在更高版本中修复,就是这样。但是,如果您还没有意识到外壳评估(参数扩展)中的错误和命令中的实际错误似乎是两回事,那么第一部分就很难理解。对于解决方案,正如您所建议的那样,我现在正尝试避免-eu并在必要时手动进行检查。
dvdgsng 2015年

1
@dvdsng-很好。那是要走的路-您应该在回答时发布脚本并奖励自己。我真的不喜欢这些选项-它们不允许以任何安全的方式进行异常处理。
mikeserv

1
@dvdsng-但是,这些选项中的任何一个都可以用在子shell上下文中。因此可以想象,您之前使用它们的任何内容都可以本地化为subshel​​l上下文,例如:(set -u; : $UNSET_VAR)和类似内容。这种东西也可以很好- &&偶尔会掉很多东西:(set -e; mkdir dir; cd dir; touch dirfile),如果您不满意的话。仅仅是这些是受控的上下文-当您将它们设置为全局选项时,您将失去控制并变得受控。不过,通常会有更有效的解决方案。
mikeserv

Answers:


15

来自man bash

  • set -u
    • 治疗尚未设定的变量和除特殊参数的其他参数"@""*"执行参数扩展时为错误。如果尝试对未设置的变量或参数进行扩展,则外壳程序将显示一条错误消息,如果不是-i交互式的,则将以非零状态退出。

POSIX指出,如果发生扩展错误,则当扩展与特殊的内置shell 无论如何通常都会忽略这一区别,因此可能无关紧要)或其他任何实用程序相关联时,非交互式shell 都应退出。。bash

  • Shell错误的后果
    • 一个膨胀误差是一个当所定义的外壳膨胀时出现字展开被执行(例如,"${x!y}"因为!不是有效的操作者) ; 如果能够在标记化期间而不是在扩展期间检测到它们,则实现可以将它们视为语法错误。
    • [A] n交互式外壳应在不退出的情况下将诊断消息写入标准错误。

也来自man bash

  • trap ... ERR
    • 如果sigspec是ERR,则在满足以下条件的情况下,只要管道(可能由单个简单命令组成),列表或复合命令返回非零退出状态,就会执行命令arg
      • ERR如果失败的命令是立即下命令列表的一部分不执行陷阱whileuntil关键字...
      • if声明中测试的一部分
      • ...在&&||列表中执行的命令的一部分,但末尾&&||... 之后的命令除外
      • ...管道中的任何命令,但最后一个...
      • ...或者使用来反转命令的返回值!
    • 这些是errexit -e选项遵循的相同条件。

上面请注意,ERR陷阱全都是关于其他命令返回的评估。但是,当发生扩展错误时,不会运行任何命令以返回任何内容。在您的示例中,echo 永远不会发生 -因为在shell评估和扩展其参数时,它会遇到一个-unset变量,该变量已由显式shell选项指定,导致立即退出当前的脚本化shell。

因此,将执行EXIT陷阱(如果有的话),并且外壳程序将以一条诊断消息退出,并且退出状态为0以外的状态-恰如其应。

至于rc:0事情,我希望这是某种特定于版本的错误-可能与两个EXIT触发器同时发生,而另一个触发另一个退出代码(不应该发生)有关。而且无论如何,使用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的命令有错误的行号。
  • 修复了导致SIGINTSIGQUITtrap在异步子Shell命令中不可用的错误。
  • 修复了中断处理导致交互式shell忽略第二个和后续SIGINT的问题。
  • 在运行trap那些信号的处理程序时,shell不再阻止接收信号,并允许大多数 trap处理程序以递归方式运行 (在trap处理trap程序执行时运行处理程序)

我认为最相关的是最后一个或第一个-或可能是两者的结合。一个trap处理程序是其本质异步的,因为它的整个工作就是等待和处理异步信号。然后您使用-eu和同时触发两个$UNSET_VAR

因此,也许您应该只进行更新,但是如果您喜欢自己,可以使用完全不同的外壳来完成。


感谢您解释如何以不同方式处理参数扩展。那为我清除了很多东西。
dvdgsng 2015年

授予您赏金是因为您的解释最有帮助。
dvdgsng

@dvdgsng-格拉西亚斯。出于好奇,您是否曾提出过自己的解决方案?
mikeserv

9

(我正在使用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

不错,一个切实可行的解决方案,可以真正增加价值!
弗洛里安·海格尔
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.