19

在 bash 手册页中,它指出:

如果管道(可能由单个简单命令组成)、
括号中的子shell命令或作为括号括起来的命令列表的一部分执行的命令之一,则立即退出...

所以我假设一个函数应该被认为是一个用大括号括起来的命令列表。但是,如果对函数调用应用条件,则 errexit 不再保留在函数体内,它会在返回之前执行整个命令列表。即使您在函数内显式创建子shell 并为该子shell 启用了errexit,命令列表中的所有命令也会执行。这是一个演示该问题的简单示例:

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

4 回答 4

17

您开始引用手册,但随后删减了解释此行为的部分,即在下一句中:

-e如果管道(可能由单个简单命令、括号括起来的子shell 命令或作为括号括起来的命令列表的一部分执行的命令之一)返回非零状态,则立即退出。如果失败的命令是紧跟在or关键字之后的命令列表的一部分、语句中测试的一部分、在or列表中执行的任何命令的一部分(除了最后一个or之后的命令之外),则shell 不会退出,一个管道,但最后一个,或者如果命令的返回状态正在与.whileuntilif&&||&&||!

于 2013-11-05T13:03:07.973 回答
11

bug-bash 邮件列表有 Eric Blake 对功能更明确的解释:

简短的回答:历史兼容性。

...

实际上,POSIX 要求的正确行为(即,在 f() 的整个主体期间完全忽略了“set -e”,因为在忽略“set -e”的上下文中调用了 f)并不直观. 但它是标准化的,所以我们必须忍受它。

还有一些关于是否set -e可以被利用来实现想要的行为的话:

因为一旦您处于忽略“set -e”的上下文中,历史行为就是对于被忽略上下文中的整个代码体,没有进一步的方法可以将其重新打开。这就是 30 年前的做法,在真正考虑 shell 函数之前,我们被这个糟糕的设计决定所困。

于 2016-05-12T15:22:18.883 回答
3

不是原始问题的答案,而是潜在问题的解决方法:设置错误陷阱:

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

于 2017-04-14T21:50:49.813 回答
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开启,因此您无需担心发生故障后执行的命令。errexitf1()

唯一的缺点是您不能设置变量,因为它f1在子shell 中运行。

于 2019-02-13T10:18:28.220 回答