为什么 bash errexit 在函数调用中没有按预期运行?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/19789102/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Why is bash errexit not behaving as expected in function calls?
提问by Craig
In the bash man page, it states:
在 bash 手册页中,它指出:
Exit immediately if a pipeline (which may consist of a single simple command),
a subshell command enclosed in parentheses, or one of the commands executed as part of a command list enclosed by braces...
如果管道(可能由单个简单命令组成)、
括在括号中的子 shell 命令或作为用大括号括起来的命令列表的一部分执行的命令之一,则立即退出...
So I assumed that a function should be considered a command list enclosed by braces. However, if you apply a conditional to the function call, errexit no longer persists inside the function body and it executes the entire command list before returning. Even if you explicitly create a subshell inside the function with errexit enabled for that subshell, all commands in the command list are executed. Here is a simple example that demonstrates the issue:
所以我假设一个函数应该被认为是一个用大括号括起来的命令列表。但是,如果对函数调用应用条件,则 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
Now if I apply a conditional to each of them...
现在,如果我对他们每个人都应用一个条件......
( 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
回答by Gareth Rees
You started to quote the manualbut then you cut the bit that explained this behaviour, which was in the very next sentence:
你开始引用手册,但随后你删掉了解释这种行为的部分,这是在下一句话中:
-e
Exit immediately if a pipeline, which may consist of a single simple command, a subshell command enclosed in parentheses, or one of the commands executed as part of a command list enclosed by braces returns a non-zero status. The shell does not exitif the command that fails is part of the command list immediately following awhile
oruntil
keyword, part of the test in anif
statement, part of any command executed in a&&
or||
list except the command following the final&&
or||
, any command in a pipeline but the last, or if the command's return status is being inverted with!
.
-e
如果管道(可能由单个简单命令、括在括号中的子 shell 命令或作为大括号括起来的命令列表的一部分执行的命令之一)返回非零状态,则立即退出。shell不会退出,如果失败的命令是紧跟在命令列表的一部分while
或until
关键字在测试中,部分if
声明,在执行任何命令的一部分&&
或||
以下的最终除了命令列表&&
或||
在任何命令一个管道但最后一个,或者如果命令的返回状态正在被!
.
回答by imz -- Ivan Zakharyaschev
bug-bash mailing listhas an explanation by Eric Blake more explicit about functions:
bug-bash 邮件列表有 Eric Blake 对函数的更明确的解释:
Short answer: historical compatibility.
...
Indeed, the correct behavior mandated by POSIX (namely, that 'set -e' is completely ignored for the duration of the entire body of f(), because f was invoked in a context that ignores 'set -e') is not intuitive. But it is standardized, so we have to live with it.
简短回答:历史兼容性。
...
实际上,POSIX 要求的正确行为(即在 f() 的整个主体期间完全忽略“set -e”,因为 f 是在忽略“set -e”的上下文中调用的)是不直观的. 但它是标准化的,所以我们必须忍受它。
And some words about whether set -e
can be exploited to achieve the wanted behavior:
还有一些关于是否set -e
可以被利用来实现想要的行为的词:
Because once you are in a context that ignores 'set -e', the historical behavior is that there is no further way to turn it back on, for that entire body of code in the ignored context. That's how it was done 30 years ago, before shell functions were really thought about, and we are stuck with that poor design decision.
因为一旦您处于忽略“set -e”的上下文中,对于被忽略上下文中的整个代码体,历史行为是没有进一步的方法将其重新打开。30 年前,在真正考虑 shell 功能之前,它就是这样做的,我们被那个糟糕的设计决定所困。
回答by VasiliNovikov
Not an answer to the original question, but a work-around for the underlying problem: set up a trapon errors:
不是原始问题的答案,而是潜在问题的解决方法:设置错误陷阱:
function on_error() {
echo "error happened!"
}
trap on_error ERR
echo "OK so far"
false
echo "this line should not execute"
The reason for the behavior itself is properly explained in other answers (basically it's expected bash behavior as per the manual and POSIX): https://stackoverflow.com/a/19789651/1091436
行为本身的原因在其他答案中得到了正确解释(基本上,按照手册和 POSIX,这是预期的 bash 行为):https: //stackoverflow.com/a/19789651/1091436
回答by bandie
not an answer but you might fix this counter intuitive behaviur by defining this helper function:
不是答案,但您可以通过定义此辅助函数来解决这种反直觉行为:
fixerrexit() { ( eval "expr '$-' : '.*e' >/dev/null && set -e; $*"; ); }
then call your functions via fixerrexit
.
然后通过调用你的函数fixerrexit
。
example:
例子:
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
if outer context has errexit
on, then fixerrexit
turns on errexit
inside f1()
as well, so you dont need to worry about commands being executed after a failure occurs.
如果外部上下文errexit
打开,则内部也fixerrexit
打开,因此您无需担心发生故障后正在执行的命令。errexit
f1()
the only downside is you can not set variables since it runs f1
inside a subshell.
唯一的缺点是您不能设置变量,因为它f1
在子外壳内运行。