需要对 Linux bash 内置 exec 命令行为的解释
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/9920512/
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
Need explanations for Linux bash builtin exec command behavior
提问by artaxerxe
From Bash Reference ManualI get the following about exec
bash builtin command:
从Bash 参考手册我得到以下关于exec
bash 内置命令的信息:
If command is supplied, it replaces the shell without creating a new process.
如果提供了命令,它将替换外壳而不创建新进程。
Now I have the following bash
script:
现在我有以下bash
脚本:
#!/bin/bash
exec ls;
echo 123;
exit 0
This executed, I got this:
这执行了,我得到了这个:
cleanup.sh ex1.bash file.bash file.bash~ output.log
(files from the current directory)
Now, if I have this script:
现在,如果我有这个脚本:
#!/bin/bash
exec ls | cat
echo 123
exit 0
I get the following output:
我得到以下输出:
cleanup.sh
ex1.bash
file.bash
file.bash~
output.log
123
My question is:
我的问题是:
If when exec
is invoked it replaces the shell without creating a new process, why when put | cat
, the echo 123
is printed, but without it, it isn't. So, I would be happy if someone can explain what's the logic of this behavior.
如果exec
调用when ,它会在不创建新进程的情况下替换 shell,为什么在 put 时会打印| cat
the echo 123
,但没有它则不会。所以,如果有人能解释这种行为的逻辑是什么,我会很高兴。
Thanks.
谢谢。
EDIT: After @torek response, I get an even harder to explain behavior:
编辑:@torek 回应后,我更难解释行为:
1.exec ls>out
command creates the out
file and put in it the ls
's command result;
1. exec ls>out
command 创建out
文件并放入其中ls
的命令结果;
2.exec ls>out1 ls>out2
creates only the files, but do not put inside any result. If the command works as suggested, I think the command number 2 should have the same result as command number 1 (even more, I think it should not have had created the out2
file).
2.exec ls>out1 ls>out2
只创建文件,但不放入任何结果。如果命令按照建议工作,我认为命令编号 2 应该与命令编号 1 具有相同的结果(甚至更多,我认为它不应该创建out2
文件)。
采纳答案by torek
In this particular case, you have the exec
in a pipeline. In order to execute a series of pipeline commands, the shell must initially fork, making a sub-shell. (Specifically it has to create the pipe, then fork, so that everything run "on the left" of the pipe can have its output sent to whatever is "on the right" of the pipe.)
在这种特殊情况下,您有exec
一个管道。为了执行一系列管道命令,shell 必须首先 fork,创建一个子 shell。(具体来说,它必须创建管道,然后分叉,以便管道“左侧”运行的所有内容都可以将其输出发送到管道“右侧”的任何内容。)
To see that this is in fact what is happening, compare:
要了解这实际上是正在发生的事情,请比较:
{ ls; echo this too; } | cat
with:
和:
{ exec ls; echo this too; } | cat
The former runs ls
without leaving the sub-shell, so that this sub-shell is therefore still around to run the echo
. The latter runs ls
by leaving the sub-shell, which is therefore no longer there to do the echo
, and this too
is not printed.
前者在ls
不离开子外壳的情况下运行,因此该子外壳仍然可以运行echo
. 后者ls
通过离开子外壳运行,因此它不再用于执行echo
,并且this too
不会打印。
(The use of curly-braces { cmd1; cmd2; }
normally suppresses the sub-shell fork action that you get with parentheses (cmd1; cmd2)
, but in the case of a pipe, the fork is "forced", as it were.)
(花括号的使用{ cmd1; cmd2; }
通常会抑制你用括号获得的子壳叉动作(cmd1; cmd2)
,但在管道的情况下,叉是“强制”的,就像它一样。)
Redirection of the current shell happens only if there is "nothing to run", as it were, after the word exec
. Thus, e.g., exec >stdout 4<input 5>>append
modifies the current shell, but exec foo >stdout 4<input 5>>append
tries to exec command foo
. [Note: this is not strictly accurate; see addendum.]
当前 shell 的重定向只有在“没有什么可运行”的情况下才会发生,就像在exec
. 因此,例如,exec >stdout 4<input 5>>append
修改当前shell,但exec foo >stdout 4<input 5>>append
尝试执行命令foo
。[注意:这不是严格准确的;见附录。]
Interestingly, in an interactive shell, after exec foo >output
fails because there is no command foo
, the shell sticks around, but stdout remains redirected to file output
. (You can recover with exec >/dev/tty
. In a script, the failure to exec foo
terminates the script.)
有趣的是,在交互式 shell 中,exec foo >output
由于没有 command 而失败后foo
,shell 会继续存在,但 stdout 仍然重定向到 file output
。(您可以使用 进行恢复exec >/dev/tty
。在脚本中,无法exec foo
终止脚本。)
向@Pumbaa80 致敬,这里有一些更能说明问题的内容:
#! /bin/bash
shopt -s execfail
exec ls | cat -E
echo this goes to stdout
echo this goes to stderr 1>&2
(note: cat -E
is simplified down from my usual cat -vET
, which is my handy go-to for "let me see non-printing characters in a recognizable way"). When this script is run, the output from ls
has cat -E
applied (on Linux this makes end-of-line visible as a $ sign), but the output sent to stdout and stderr (on the remaining two lines) is notredirected. Change the | cat -E
to > out
and, after the script runs, observe the contents of file out
: the final two echo
s are not in there.
(注意:cat -E
简化了我通常的cat -vET
,这是我方便的“让我以可识别的方式看到非打印字符”的方法)。运行此脚本时,输出ls
已cat -E
应用(在 Linux 上,这使得行尾可见为 $ 符号),但发送到 stdout 和 stderr(在其余两行上)的输出不会重定向。更改| cat -E
为> out
,脚本运行后,观察 file 的内容out
:最后两个echo
s 不在那里。
Now change the ls
to foo
(or some other command that will not be found) and run the script again. This time the output is:
现在更改ls
为foo
(或其他一些找不到的命令)并再次运行脚本。这次的输出是:
$ ./demo.sh
./demo.sh: line 3: exec: foo: not found
this goes to stderr
and the file out
now has the contents produced by the first echo
line.
并且该文件out
现在具有第一echo
行生成的内容。
This makes what exec
"really does" as obvious as possible (but no more obvious, as Albert Einstein did not put it :-) ).
这使得exec
“真正做了”的事情尽可能明显(但不再明显,因为阿尔伯特爱因斯坦没有说:-))。
Normally, when the shell goes to execute a "simple command" (see the manual page for the precise definition, but this specifically excludes the commands in a "pipeline"), it prepares any I/O redirection operations specified with <
, >
, and so on by opening the files needed. Then the shell invokes fork
(or some equivalent but more-efficient variant like vfork
or clone
depending on underlying OS, configuration, etc), and, in the child process, rearranges the open file descriptors (using dup2
calls or equivalent) to achieve the desired final arrangements: > out
moves the open descriptor to fd 1—stdout—while 6> out
moves the open descriptor to fd 6.
通常,当 shell 开始执行“简单命令”时(请参阅手册页了解精确定义,但这明确排除了“管道”中的命令),它会准备用<
,指定的任何 I/O 重定向操作>
,等等通过打开所需的文件。然后 shell 调用fork
(或一些等效但更有效的变体,如vfork
或clone
取决于底层操作系统、配置等),并在子进程中重新排列打开的文件描述符(使用dup2
调用或等效项)以实现所需的最终安排:> out
将打开的描述符移动到 fd 1——stdout——同时6> out
将打开的描述符移动到 fd 6。
If you specify the exec
keyword, though, the shell suppresses the fork
step. It does all the file opening and file-descriptor-rearranging as usual, but this time, it affects any and all subsequent commands. Finally, having done all the redirections, the shell attempts to execve()
(in the system-call sense) the command, if there is one. If there is no command, or if the execve()
call fails andthe shell is supposed to continue running (is interactive or you have set execfail
), the shell soldiers on. If the execve()
succeeds, the shell no longer exists, having been replaced by the new command. If execfail
is unset and the shell is not interactive, the shell exits.
exec
但是,如果您指定关键字,shell 会禁止该fork
步骤。它像往常一样执行所有文件打开和文件描述符重新排列,但这一次,它会影响任何和所有后续命令。最后,完成所有重定向后,shell 尝试execve()
(在系统调用意义上)命令,如果有的话。如果没有命令,或者如果execve()
调用失败并且shell 应该继续运行(是交互式的或者你已经设置了execfail
),shell 就会继续运行。如果execve()
成功,则外壳不再存在,已被新命令替换。如果execfail
未设置并且 shell 不是交互式的,shell 将退出。
(There's also the added complication of the command_not_found_handle
shell function: bash's exec
seems to suppress running it, based on test results. The exec
keyword in general makes the shell not look at its own functions, i.e., if you have a shell function f, running f
as a simple command runs the shell function, as does (f)
which runs it in a sub-shell, but running (exec f)
skips over it.)
(还有command_not_found_handle
shell 函数的额外复杂性:exec
根据测试结果,bash似乎禁止运行它。该exec
关键字通常会使 shell 不查看自己的函数,即,如果您有一个 shell 函数 f,则f
作为simple command 运行 shell 函数,就像(f)
在子 shell 中运行它一样,但运行会(exec f)
跳过它。)
至于为什么要
ls>out1 ls>out2
ls>out1 ls>out2
创建两个文件(有或没有exec
exec
),这很简单:shell 打开每个重定向,然后使用dup2
dup2
来移动文件描述符。如果您有两个普通>
>
重定向,shell 会同时打开两个重定向,将第一个重定向到 fd 1 (stdout),然后将第二个移动到 fd 1 (再次 stdout),从而关闭进程中的第一个。最后,它运行ls ls
ls ls
,因为这是删除>out1 >out2
>out1 >out2
. 只要没有名为ls
ls
的文件,该ls
ls
命令就会向 stderr 抱怨,并且不会向 stdout 写入任何内容。