在bash手册页中,它指出:
如果管道(可能包含一个简单的命令)立即退出,
括在括号中的子shell命令,或执行为的命令之一 大括号括起来的命令列表的一部分......
所以我假设一个函数应该被视为括号括起来的命令列表。但是,如果对函数调用应用条件,则errexit不再在函数体内持久存在,并且在返回之前执行整个命令列表。即使您在为该子shell启用了errexit的函数内显式创建子shell,也会执行命令列表中的所有命令。这是一个演示该问题的简单示例:
function a() { b ; c ; d ; e ; }
function ap() { { b ; c ; d ; e ; } ; }
function as() { ( set -e ; b ; c ; d ; e ) ; }
function b() { false ; }
function c() { false ; }
function d() { false ; }
function e() { false ; }
( set -Eex ; a )
+ a
+ b
+ false
( set -Eex ; ap )
+ ap
+ b
+ false
( set -Eex ; as )
+ as
+ set -e
+ b
+ false
现在,如果我对每个人都应用条件......
( set -Eex ; a || false )
+ a
+ b
+ false
+ c
+ false
+ d
+ false
+ e
+ false
+ false
( set -Eex ; ap || false )
+ ap
+ b
+ false
+ c
+ false
+ d
+ false
+ e
+ false
+ false
( set -Eex ; as )
+ as
+ set -e
+ b
+ false
+ c
+ false
+ d
+ false
+ e
+ false
+ false
答案 0 :(得分:11)
你开始引用manual但是你切断了解释这种行为的位,这位于下一句话中:
-e
如果管道可能包含一个简单的命令,括在括号中的子shell命令,或作为由括号括起的命令列表的一部分执行的命令之一返回非零,则立即退出状态。 如果失败的命令是紧跟在while
或until
关键字之后的命令列表的一部分,那么shell不会退出,if
中的部分测试语句,在&&
或||
列表中执行的任何命令的一部分,但最后&&
或||
之后的命令除外,管道但最后一个,或者命令的返回状态是否被!
反转。
答案 1 :(得分:7)
bug-bash mailing list有一个Eric Blake对函数更明确的解释:
简答:历史兼容性。
...
确实,POSIX要求的正确行为(即,设置-e'是 完全忽略整个f()体的持续时间,因为f 在忽略&set -e')不直观的上下文中调用。但 它是标准化的,所以我们必须忍受它。
关于是否可以利用set -e
来实现所需行为的一些说法:
因为一旦你处于一个忽略&set -e'的历史背景中,那就是历史 行为是没有进一步的方法来重新打开它 被忽略的上下文中的整个代码体。那是怎么做的30 几年前,在真正考虑shell函数之前,我们是 坚持那个糟糕的设计决定。
答案 2 :(得分:1)
不是原始问题的答案,而是针对潜在问题的解决方法:在错误上设置陷阱:
function on_error() {
echo "error happened!"
}
trap on_error ERR
echo "OK so far"
false
echo "this line should not execute"
行为本身的原因在其他答案中得到了恰当的解释(基本上是根据手册和POSIX预期的bash行为):https://stackoverflow.com/a/19789651/1091436
答案 3 :(得分:0)
不是答案,但是您可以通过定义以下辅助功能来解决此反常的直观行为:
fixerrexit() { ( eval "expr '$-' : '.*e' >/dev/null && set -e; $*"; ); }
然后通过fixerrexit
调用函数。
示例:
f1()
{
mv unimportant-0.txt important.txt
rm unimportant-*.txt
}
set -e
if fixerrexit f1
then
echo here is the important file: important.txt
echo unimportant files are deleted
fi
如果外部上下文启用了errexit
,那么fixerrexit
也会在errexit
内部启用f1()
,因此您不必担心发生故障后将执行命令
唯一的缺点是您无法设置变量,因为它在子shell中运行f1
。