英文:
How to determine when Bash's "exit immediately" option (set -e) is being ignored
问题
Bash shell的手册在描述set -e
内置选项的部分提供了以下信息:
如果复合命令或shell函数在忽略
-e
的上下文中执行,那么复合命令或函数体内执行的所有命令都不会受到-e
设置的影响,即使-e
被设置并且命令返回失败状态。如果复合命令或shell函数在忽略-e
的上下文中执行时设置了-e
,那么该设置直到复合命令或包含函数调用的命令完成后才会生效。
我在编写的Bash脚本中遇到了一个问题,我设置了-e
标志,但是当命令返回非零退出状态码时,脚本并没有退出。这发生在一个被调用的函数内部,例如:
代码清单1
set -e
function foo ()
{
set -e
ls zzzz # 'zzzz'不存在
... # <- 这些语句会被执行
}
foo
... # <- 这些语句会被执行
我知道可以使用Bash的内置变量$-
来显示当前设置的选项。当我在实际脚本中这样做时,-e
选项被设置为ehuB
。然而,当被调用的函数执行返回非零退出状态的语句时,该函数并没有退出。所以,我认为被调用的函数必须在忽略-e
的上下文中执行。
代码清单2
set -e
function foo ()
{
set -e
echo "settings=$-" # ehuB
ls zzzz # 'zzzz'不存在
... # <- 这些语句会被执行
}
echo "settings=$-" # ehuB
foo
... # <- 这些语句会被执行
所以我的问题是:如何以编程方式测试确定Bash脚本中的一段代码是否在忽略-e
的上下文中执行?
此外,什么条件会创建一个忽略-e
选项(当设置时)的执行上下文?
英文:
The manual for the Bash shell provides the following information in the section that describes the set -e
builtin option:
> If a compound command or shell function executes in a context where -e is being ignored, none of the commands executed within the compound command or function body will be affected by the -e setting, even if -e is set and a command returns a failure status. If a compound command or shell function sets -e while executing in a context where -e is ignored, that setting will not have any effect until the compound command or the command containing the function call completes.
I've encountered a problem with a Bash script I'm writing where I'm setting the -e
flag, but the script does not exit when a command returns a non-zero exit status code. This is occurring within a called function, e.g.,
Listing 1
set -e
function foo ()
{
set -e
ls zzzz # 'zzzz' doesn't exist
... # <- these statements are executed
}
foo
... # <- these statements are executed
I know I can use Bash's builtin variable $-
to display the options that are currently set. When I do this in my actual script, the -e
option is set: ehuB
. Nevertheless, the called function does not exit when it executes a statement that returns a non-zero exit status. So, I'm assuming the called function must be executing within a context where -e
is being ignored.
Listing 2
set -e
function foo ()
{
set -e
echo "settings=$-" # ehuB
ls zzzz # 'zzzz' doesn't exist
... # <- these statements are executed
}
echo "settings=$-" # ehuB
foo
... # <- these statements are executed
So my question is this: how can I programmatically test to determine that a section of code in a Bash script is or isn't executing within a context where -e
is being ignored?
Additionally, what conditions create an execution context where the -e
option (when set) is ignored?
答案1
得分: 1
什么条件会创建一个执行上下文,在该上下文中忽略了-e选项(当设置时)?
在文档中有说明:
-e
如果一个管道(参见Pipelines)返回一个非零状态,立即退出。这个管道可以由一个简单命令(参见Simple Commands),一个命令列表(参见Lists of Commands)或一个复合命令(参见Compound Commands)组成。如果失败的命令是紧随while或until关键字的命令列表中的一部分,if语句中的测试的一部分,&&或||列表中除最后一个命令之外的任何命令,管道中的任何命令但最后一个命令,或者命令的返回状态被!反转,shell不会退出。如果一个复合命令(而不是子shell)返回一个非零状态,因为-e被忽略,shell不会退出。如果设置了ERR的陷阱,则在shell退出之前执行。
此选项适用于shell环境和每个子shell环境(参见Command Execution Environment),可能会导致子shell在执行所有命令之前退出。
如果复合命令或shell函数在忽略-e的上下文中执行,则不会影响复合命令或函数体内执行的任何命令的-e设置,即使-e被设置并且命令返回失败状态。如果复合命令或shell函数在忽略-e的上下文中执行时设置了-e,则该设置在复合命令或包含函数调用的命令完成之前不会产生任何效果。
如何确定Bash的“立即退出”选项(set -e)是否被忽略
这是我的决策树:
- 命令是否从if、while或until调用?-> 被忽略
- 命令是否以
!
为前缀?-> 被忽略 - 命令是否在export、declare、typeset或local的赋值中?-> 被忽略
- 不被忽略。
还要记住继承:
- 这是一个异步命令吗?
- 这是一个进程替换,比如
echo < <(false; echo test >&2)
? - 这是在管道的一部分,比如
{ false; echo test >&2; } | echo
? - 这是在coproc中吗?
- 那么set -e会被继承
- 这是一个进程替换,比如
- 你在一个子shell中吗()?
- 那么errexit_inherit设置了吗?
还有一个逻辑,命令链的实际退出状态如何。
- 在99%的情况下,一组命令的退出状态是最后一个执行的命令的退出状态
a && b || c
与a || b && c
的退出状态是什么?- 是否设置了lastpipe?
if
块的退出状态是什么?(最后一个执行的命令)。例如set -e; ( if true; then false; fi )
?
请注意,我在“我的”脚本中使用了set -e
。我不会强制别人使用它。而且有一些脚本,当其中一个命令失败时,你希望继续执行。
如何以编程方式测试确定Bash脚本中的一段代码是否在忽略-e的上下文中执行?
你不能。
你可以编写一个bash内置命令来访问exit_immediately_on_error
,但在执行内置命令之前,它会在这里被重置https://github.com/bminor/bash/blob/ec8113b9861375e4e17b3307372569d429dec814/execute_cmd.c#L4890。此外,检查并不像https://github.com/bminor/bash/blob/ec8113b9861375e4e17b3307372569d429dec814/execute_cmd.c#L2792那样简单,所以我看不到有办法。
英文:
> what conditions create an execution context where the -e option (when set) is ignored?
Is documented in:
> -e
>
> Exit immediately if a pipeline (see Pipelines), which may consist of a
> single simple command (see Simple Commands), a list (see Lists of
> Commands), or a compound command (see Compound Commands) returns a
> non-zero status. The shell does not exit if the command that fails is
> part of the command list immediately following a while or until
> keyword, part of the test in an if 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 !. If a compound command other
> than a subshell returns a non-zero status because a command failed
> while -e was being ignored, the shell does not exit. A trap on ERR, if
> set, is executed before the shell exits.
>
> This option applies to the shell environment and each subshell
> environment separately (see Command Execution Environment), and may
> cause subshells to exit before executing all the commands in the
> subshell.
>
> If a compound command or shell function executes in a context where -e
> is being ignored, none of the commands executed within the compound
> command or function body will be affected by the -e setting, even if
> -e is set and a command returns a failure status. If a compound command or shell function sets -e while executing in a context where
> -e is ignored, that setting will not have any effect until the compound command or the command containing the function call
> completes.
> How to determine when Bash's "exit immediately" option (set -e) is being ignored
This is like my decision tree:
- Is the command called from if, while or until? -> ignored
- Is the command prefixed with
!
? -> ignored - Is the command in assignment of export, declare, typeset or local? -> ignored
- Not ignored.
Also remember about inheritance:
- Is this an asynchronous command?
- Is this a process substitution, like
echo < <(false; echo test >&2)
? - Is this inside a part of a pipeline, like
{ false; echo test >&2; } | echo
? - Is this inside coproc?
- then set -e is inherited
- Is this a process substitution, like
- Are you in a subshell () ?
- then is errexit_inherit set?
There is also a logic what is the actual exit status of chain of commands like.
- in 99% of contexts, the exit status of a block of commands is the exit status of last command executed
- What is the exit status of
a && b || c
vsa || b && c
? - Is lastpipe set?
- What is the exit status of
if
block? (last command executed). Ex.set -e; ( if true; then false; fi )
?
Note that I am using set -e
in my scripts. I am not forcing it on other people. And there are scripts that you want to continue when one of the commands fails.
> how can I programmatically test to determine that a section of code in a Bash script is or isn't executing within a context where -e is being ignored?
You can't.
You could write a bash builtin to access exit_immediately_on_error
but it is reset here https://github.com/bminor/bash/blob/ec8113b9861375e4e17b3307372569d429dec814/execute_cmd.c#L4890 before executing the builtin. Also, the checks are not really simple like https://github.com/bminor/bash/blob/ec8113b9861375e4e17b3307372569d429dec814/execute_cmd.c#L2792 so I do not see a way.
答案2
得分: 1
我可以将这部分放在评论区,但为了格式的目的,我将其作为答案发布。
你能运行以下脚本并发布结果吗:
#!/bin/bash
set -e
trap
function foo ()
{
set -e
echo $BASH_VERSION
ls zzzz
echo "After ls"
}
echo "$(foo)"
echo "After subshell of foo"
echo -e "\nNow, going to do a direct call..."
foo
echo "After direct call of foo"
我的结果如下:
ls: 无法访问 'zzzz': 没有那个文件或目录
5.1.16(1)-release
After subshell of foo
Now, going to do a direct call...
5.1.16(1)-release
ls: 无法访问 'zzzz': 没有那个文件或目录
退出码:2
英文:
I could have put this in comments area, but put as an answer for formatting purposes.
Can you run following script and post results :
#!/bin/bash
set -e
trap
function foo ()
{
set -e
echo $BASH_VERSION
ls zzzz
echo "After ls"
}
echo "$(foo)"
echo "After subshell of foo"
echo -e "\nNow, going to do a direct call..."
foo
echo "After direct call of foo"
Mine gives result :
ls: cannot access 'zzzz': No such file or directory
5.1.16(1)-release
After subshell of foo
Now, going to do a direct call...
5.1.16(1)-release
ls: cannot access 'zzzz': No such file or directory
Exit code : 2
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论