2016-12-06 125 views
2

让我们想象一下,我们有这样的代码(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 

我明白$(...)启动子shell set -e没有传播,所以我的问题是:什么是使这个运行如预期没有太多混乱的惯用方法?我可以看到3种解决方案,我都不喜欢这些解决方案(但我确实无法确定它们确实可行):1)将set -e添加到f(以及该应用中的所有其他功能)的开头。 2)运行$(set -e && f)。 3)将... || return 1添加到每个可能失败的命令。

+2

选项3实际上是一个[好主意](http://mywiki.wooledge.org/BashFAQ/105)。 – chepner

+0

@chepner:很好阅读。 'cmd ||返回1'是明确的,概念上没问题,但这么麻烦...... – tokland

回答

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 
" 
+1

非常有趣,我不知道这种方法。虽然写起来有点麻烦。我会在'f'中添加一些回声来使'declare -p out'显示一些有意义的东西。 – tokland

+0

是的,这是正确的输出,因为之后'false'命令使功能'f'退出。 – anubhava

+1

有趣的替代方法--' set -e'确实也适用于_process_替换中的命令(与_command_替换相对),尽管失败将不会中止整个脚本。请注意,流程替换中的命令在_subshel​​l中运行too_,您可以验证如下:'f(){echo $((++ v)); }; V = 1;猫<(f);回声“$ v”' – mklement0

3

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

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

输出(标准错误),如果你把这个脚本(退出代码后会1):

[f] Start 

注:

  • 根据设计,set -e/trap ERR只对不属于条件的故障作出响应(参见man bash,在set(搜索文字“set [”)的描述下,确切的规则,在Bash 3.x和4.x之间略有改变)。

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

    • 要退出的功能/子外壳明确在这种情况下,使用像|| return 1/|| exit 1,或调用函数/子外壳分别外部条件的第一;例如,在[[ $(f) == 'foo' ]]情况:res=$(f); [[ $res == 'foo' ]] - res=$(f)会触发当前shell的陷阱。

  • 至于为什么trap代码可以被调用多个倍:在手边的情况下,false内部f()第一触发陷阱,然后,由于阱的exit 1离开subshel​​l$(f)),陷阱被触发再次目前壳(运行脚本的人)。

+1

谢谢!我用'trap ... ERR'进行了测试,但是'set -E'丢失了,无法工作。我认为这是一个非常酷的解决方法,您不需要更改代码本身。 – tokland

+0

当我从if调用'f'时有问题:'if! F;然后回显“f失败”; fi' - >'[f]失败!我不希望这样执行' – tokland

+1

@tokland:恐怕这是设计 - 请参阅我的更新。 – mklement0