Bash 在shell脚本中强制使用set-e的惯用方法

Bash 在shell脚本中强制使用set-e的惯用方法,bash,shell,error-handling,Bash,Shell,Error Handling,让我们假设我们有这个代码script.sh: 输出: $ bash myscript.sh [f] Start [f] Fail! I don't want this executed 我知道。。。在不传播set-e的情况下启动一个子shell,所以我的问题是:在没有太多混乱的情况下,如何按照预期运行?我可以看到3种解决方案,我不喜欢也不确定它们是否真的有效:1将set-e添加到f的开头以及应用程序中的所有其他函数。2运行$set-e和&f。3加上……||对每个可能失败的命令返回1。您应该使用

让我们假设我们有这个代码script.sh:

输出:

$ bash myscript.sh
[f] Start
[f] Fail! I don't want this executed
我知道。。。在不传播set-e的情况下启动一个子shell,所以我的问题是:在没有太多混乱的情况下,如何按照预期运行?我可以看到3种解决方案,我不喜欢也不确定它们是否真的有效:1将set-e添加到f的开头以及应用程序中的所有其他函数。2运行$set-e和&f。3加上……||对每个可能失败的命令返回1。

您应该使用来调用函数,而不是命令替换,以便set-e仍然有效:

mapfile arr < <(f)   # call function f using process substitution
out="${arr[*]}"         # convert array content into a string
declare -p out          # check output

这不是最漂亮的解决方案,但它确实允许您为当前shell以及任何函数和子shell模拟set-e:

如果以后调用此脚本,则输出到stderr的退出代码将为1-请注意,没有打印到stderr>&2的第二个回显,这证明执行false中止了命令替换:

[f] Start
注:

根据设计,set-e/trap-ERR只对不属于条件集的故障做出响应。有关确切的规则,请参见bash 3.x和4.x之间稍有变化的set-search-for-literal set[描述下的man bash

因此,例如,f不会在以下命令中触发陷阱:if!f;then…,f&&echo ok;以下命令会在子shell命令替换$…,但不会在封闭的条件[…]中触发陷阱:[[$f=='foo']]]&&echo ok,因此整个脚本不会中止

要在这种情况下显式退出函数/子shell,请使用类似于| | return 1/| | | | | | | | | | | | | | | | | | | exit 1的方法,或者在条件优先之外单独调用函数/子shell;例如,在[$f=='foo']]情况下:res=='f也将触发当前shell的陷阱

至于陷阱代码可能被多次调用的原因:在本例中,f中的false首先触发陷阱,然后,因为陷阱的出口1退出子shell$f,所以对于运行脚本的当前shell,陷阱再次被触发


非常有趣,我不知道这种方法。不过写起来有点麻烦。我将在f中添加一些回音,使declare-p out显示一些有意义的内容。是的,这是正确的输出,因为在该错误命令之后,函数f退出。有趣的替代选项-set-e确实适用于进程替换中的命令以及o与命令替换相反,尽管失败不会导致整个脚本中止。请注意,进程替换中的命令也在子shell中运行,您可以按如下方式进行验证:f{echo$++v;};v=1;cat-Yes你是对的。因此set-e在进程替换中对subshell有效,但在命令替换中无效。谢谢!我已经用trap进行了测试…呃,但是set-e没有发挥作用。我认为这是一个非常酷的解决方法,你不需要更改代码本身。当从if:if!f;然后echo f调用f时,我遇到了问题失败;fi->[f]失败!我不想要这个executed@tokland:恐怕这是出于设计-请看我的更新。set-E和解释良好的答案++选项3的有趣用法实际上是一个。@chepner:Good read.cmd | | return 1在概念上是明确的,但很麻烦。。。
[f] Start
declare -- out="f:before-false1
 f:before-false2
"
#!/bin/bash

# Set up an ERR trap that unconditionally exits with a nonzero exit code.
# Similar to -e, this trap is invoked when a command reports a nonzero
# exit code (outside of a conditional / test).
# Note: This trap may be called *multiple* times. 
trap 'exit 1' ERR

# Now ensure that the ERR trap is called not only by the current shell,
# but by any subshells too:
# set -E (set -o errtrace) causes functions and subshells to inherit
# ERR traps.
set -E    

f() {
  echo "[f] Start"  >&2
  echo "f:before-false1"
  echo "f:before-false2"
  false
  echo "f:after-false"
  echo "[f] Fail! I don't want this executed" >&2
}

out=$(f)
[f] Start