如何获得“set -e”的效果和用处?在 shell 函数内?
set -e
(或以#!/bin/sh -e
开头的脚本)对于在出现问题时自动轰炸非常有用。它使我不必对每个可能失败的命令进行错误检查。
如何在函数内获得与此等效的内容?
例如,我有以下脚本,该脚本在出现错误时立即退出,并显示错误退出状态:
#!/bin/sh -e
echo "the following command could fail:"
false
echo "this is after the command that fails"
输出符合预期:
the following command could fail:
现在我想将其包装到函数中:
#!/bin/sh -e
my_function() {
echo "the following command could fail:"
false
echo "this is after the command that fails"
}
if ! my_function; then
echo "dealing with the problem"
fi
echo "run this all the time regardless of the success of my_function"
预期输出:
the following command could fail:
dealing with the problem
run this all the time regardless of the success of my_function
实际输出:(
the following output could fail:
this is after the command that fails
run this all the time regardless of the success of my_function
即函数忽略set -e
)
这可能是预期的行为。我的问题是:如何在 shell 函数内获得 set -e
的效果和用处?我希望能够进行一些设置,这样我就不必单独检查每个调用的错误,但脚本将在遇到错误时停止。它应该根据需要展开堆栈,直到我检查结果为止,或者如果我没有检查结果,则退出脚本本身。这就是 set -e
已经做的事情,只是它不嵌套。
set -e
(or a script starting with #!/bin/sh -e
) is extremely useful to automatically bomb out if there is a problem. It saves me having to error check every single command that might fail.
How do I get the equivalent of this inside a function?
For example, I have the following script that exits immediately on error with an error exit status:
#!/bin/sh -e
echo "the following command could fail:"
false
echo "this is after the command that fails"
The output is as expected:
the following command could fail:
Now I'd like to wrap this into a function:
#!/bin/sh -e
my_function() {
echo "the following command could fail:"
false
echo "this is after the command that fails"
}
if ! my_function; then
echo "dealing with the problem"
fi
echo "run this all the time regardless of the success of my_function"
Expected output:
the following command could fail:
dealing with the problem
run this all the time regardless of the success of my_function
Actual output:
the following output could fail:
this is after the command that fails
run this all the time regardless of the success of my_function
(ie. the function is ignoring set -e
)
This presumably is expected behaviour. My question is: how do I get the effect and usefulness of set -e
inside a shell function? I'd like to be able to set something up such that I don't have to individually error check every call, but the script will stop on encountering an error. It should unwind the stack as far as is needed until I do check the result, or exit the script itself if I haven't checked it. This is what set -e
does already, except it doesn't nest.
I've found the same question asked outside Stack Overflow but no suitable answer.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(11)
我最终选择了这个,这显然是有效的。我一开始尝试了导出方法,但后来发现我需要导出脚本使用的每个全局(常量)变量。
禁用
set -e
,然后在启用了set -e
的子 shell 中运行函数调用。将子shell的退出状态保存在变量中,重新启用set -e,然后测试该变量。您不能将
f
作为管道的一部分运行,也不能作为||
命令列表的&&
的一部分运行(除非管道或列表中的最后一个命令),或作为if
或while
中的条件,或其他忽略set -e
的上下文。 此代码也不能出现在任何这些上下文中,因此,如果您在函数中使用此代码,调用者必须使用相同的 subshell / save-exit-status 技巧。考虑到限制和难以阅读的语法,这种使用set -e
实现类似于抛出/捕获异常的语义并不真正适合一般用途。trap err_handler_function ERR
与set -e
具有相同的限制,因为它不会在set -e
不会触发的上下文中触发错误t 在命令失败时退出。您可能认为以下内容可行,但事实并非如此:
set -e
在子 shell 内不起作用,因为它记住它位于if
的条件内。我认为作为一个子 shell 会改变这一点,但只有在一个单独的文件中并在其上运行一个完整的单独 shell 才可以工作。I eventually went with this, which apparently works. I tried the export method at first, but then found that I needed to export every global (constant) variable the script uses.
Disable
set -e
, then run the function call inside a subshell that hasset -e
enabled. Save the exit status of the subshell in a variable, re-enable set -e, then test the var.You can't run
f
as part of a pipeline, or as part of a&&
of||
command list (except as the last command in the pipe or list), or as the condition in anif
orwhile
, or other contexts that ignoreset -e
. This code also can't be in any of those contexts, so if you use this in a function, callers have to use the same subshell / save-exit-status trickery. This use ofset -e
for semantics similar to throwing/catching exceptions is not really suitable for general use, given the limitations and hard-to-read syntax.trap err_handler_function ERR
has the same limitations asset -e
, in that it won't fire for errors in contexts whereset -e
won't exit on failed commands.You might think the following would work, but it doesn't:
set -e
doesn't take effect inside the subshell because it remembers that it's inside the condition of anif
. I thought being a subshell would change that, but only being in a separate file and running a whole separate shell on it would work.来自
set -e
的文档:在您的情况下,
false
是管道的一部分,前面是!
和 的一部分if
。因此,解决方案是重写您的代码,使其不再存在。换句话说,这里的函数没有什么特别的。尝试:
From documentation of
set -e
:In your case,
false
is a part of a pipeline preceded by!
and a part ofif
. So the solution is to rewrite your code so that it isn't.In other words, there's nothing special about functions here. Try:
您可以直接使用子 shell 作为函数定义,并使用
set -e
将其设置为立即退出。这会将set -e
的范围限制为仅函数子 shell,并且稍后可以避免在set +e
和set -e
之间切换。此外,您可以在
if
测试中使用变量赋值,然后在附加的else
语句中回显结果。You may directly use a subshell as your function definition and set it to exit immediately with
set -e
. This would limit the scope ofset -e
to the function subshell only and would later avoid switching betweenset +e
andset -e
.In addition, you can use a variable assignment in the
if
test and then echo the result in an additionalelse
statement.这有点混乱,但你可以这样做:
如果你的 shell 支持 export -f (bash 支持),这将起作用。
请注意,这不会终止脚本。回声
f 中的 false 后不会执行,主体也不会执行
if 的一部分,但是 if 后面的语句会被执行。
如果您使用的 shell 不支持 export -f,您可以
通过在函数中运行 sh 来获取所需的语义:
This is a bit of a kludge, but you can do:
This will work if your shell supports export -f (bash does).
Note that this will not terminate the script. The echo
after the false in f will not execute, nor will the body
of the if, but statements after the if will be executed.
If you are using a shell that does not support export -f, you can
get the semantics you want by running sh in the function:
注意/编辑:正如评论者指出的那样,这个答案使用
bash
,而不是像他的问题中使用的 OP 那样的sh
。当我最初发布答案时,我错过了这个细节。无论如何,我都会保留这个答案,因为某些路人可能会对它感兴趣。你准备好了吗?
这是一种利用 DEBUG 陷阱来实现此目的的方法,该陷阱在每个命令之前运行,并且会产生类似于其他语言中的整个异常/try/catch 习惯用法的错误。看看吧。我已经让你的例子又加深了一次“调用”。
和输出:
我还没有在野外测试过这个,但是在我的脑海中,有很多优点:
实际上并没有那么慢。我在使用和不使用
functrace
选项的紧密循环中运行了该脚本,并且在 10 000 次迭代下时间非常接近。您可以扩展此 DEBUG 陷阱来打印堆栈跟踪而无需对 $FUNCNAME 和 $BASH_LINENO 进行整个循环。你可以免费得到它(除了实际做回声线)。
不必担心
shopt -s Heritage_errexit
问题。Note/Edit: As a commenter pointed out, this answer uses
bash
, and notsh
like the OP used in his question. I missed that detail when I originaly posted an answer. I will leave this answer up anyway since it might be interested to some passerby.Y'aaaaaaaaaaaaaaaaaaallll ready for this?
Here's a way to do it with leveraging the DEBUG trap, which runs before each command, and sort of makes errors like the whole exception/try/catch idioms from other languages. Take a look. I've made your example one more 'call' deep.
and the output:
I haven't tested this in the wild, but off the top of my head, there are a bunch of pros:
It's actually not that slow. I've ran the script in a tight loop with and without the
functrace
option, and times are very close to each other under 10 000 iterations.You could expand on this DEBUG trap to print a stack trace without doing that whole looping over $FUNCNAME and $BASH_LINENO nonsense. You kinda get it for free (besides actually doing an echo line).
Don't have to worry about that
shopt -s inherit_errexit
gotcha.使用
&&
运算符连接函数中的所有命令。这并不是太麻烦,并且会给出您想要的结果。Join all commands in your function with the
&&
operator. It's not too much trouble and will give the result you want.这是由设计和 POSIX 规范决定的。我们可以在
man bash
中读到:因此,您应该避免在函数中依赖
set -e
。给出以下示例 Austin Group:
函数中的
set -e
被忽略,因为该函数是 AND-OR 列表中的命令,而不是最后一个命令。上述行为是 POSIX 指定和要求的(请参阅:所需操作):
This is by design and POSIX specification. We can read in
man bash
:therefore you should avoid relying on
set -e
within functions.Given the following exampleAustin Group:
the
set -e
is ignored within the function, because the function is a command in an AND-OR list other than the last.The above behaviour is specified and required by POSIX (see: Desired Action):
我知道这不是您所要求的,但您可能知道也可能不知道您寻求的行为内置于“make”中。 “make”过程的任何部分失败都会中止运行。不过,这是一种与 shell 脚本完全不同的“编程”方式。
I know this isn't what you asked, but you may or may not be aware that the behavior you seek is built into "make". Any part of a "make" process that fails aborts the run. It's a wholly different way of "programming", though, than shell scripting.
您需要在子 shell 中调用函数(在方括号 () 内)才能实现此目的。
我想你想这样写你的脚本:
然后输出是(根据需要):
You will need to call your function in a sub shell (inside brackets ()) to achieve this.
I think you want to write your script like this:
Then the output is (as desired):
如果子shell不是一个选项(假设你需要做一些疯狂的事情,比如设置一个变量),那么你可以检查每个可能失败的命令并通过附加
|| 来处理它。返回 $?
。这会导致函数在失败时返回错误代码。它很丑陋,但它确实
有效
If a subshell isn't an option (say you need to do something crazy like set a variable) then you can just check every single command that might fail and deal with it by appending
|| return $?
. This causes the function to return the error code on failure.It's ugly, but it works
gives
基于@antak的答案的可重用
bash
函数:使用示例:
输出:
详细说明此处。
Reusable
bash
function based on the answer by @antak:Usage example:
Output:
Details are explained here.