在shell脚本中强制设置-e的惯用方法

时间:2016-12-06 18:40:39

标签: bash shell error-handling

我们假设我们有这段代码(script.sh):

#!/bin/bash
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)

输出:

$ 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)将... || return 1添加到可能失败的每个命令。

2 个答案:

答案 0 :(得分:3)

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

#!/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)

如果你调用这个脚本输出(到stderr)(之后的退出代码将是1):

[f] Start

注意:

  • 按照设计,set -e / trap ERR仅响应不属于条件的失败(请参阅man bashset(搜索文字“set [”),确切的规则,在Bash 3.x和4.x之间略有变化。

    • 因此,例如,f不会在以下命令中触发陷阱:if ! f; then ...f && echo ok;以下内容触发子shell中的陷阱 (命令替换$(...),但不包含封闭的条件([[ ... ]]):[[ $(f) == 'foo' ]] && echo ok,因此脚本作为一个整体不会中止。

    • 要在这种情况下明确退出函数/子shell ,请使用|| return 1 / || exit 1之类的内容,或分别调用函数/ subshel​​l <条件优先的;例如,在[[ $(f) == 'foo' ]]情况下:res=$(f); [[ $res == 'foo' ]] - res=$(f)也将触发当前shell的陷阱。

  • 至于为什么可以调用trap代码多次次:在手头的情况下,false {{ 1}} first 触发陷阱,然后,因为陷阱的f()退出子shell exit 1),陷阱被触发再次 当前 shell(运行脚本的那个)。

答案 1 :(得分:1)

而不是命令替换,您应该使用process substitution来调用您的函数,以便set -e保持有效:

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

<强>输出:

[f] Start
declare -- out="f:before-false1
 f:before-false2
"