为什么bash errexit在函数调用中的表现不如预期?

时间:2013-11-05 12:31:47

标签: bash

在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

4 个答案:

答案 0 :(得分:11)

你开始引用manual但是你切断了解释这种行为的位,这位于下一句话中:

  

-e如果管道可能包含一个简单的命令,括在括号中的子shell命令,或作为由括号括起的命令列表的一部分执行的命令之一返回非零,则立即退出状态。 如果失败的命令是紧跟在whileuntil关键字之后的命令列表的一部分,那么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