需要对 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

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-06 05:28:32  来源:igfitidea点击:

Need explanations for Linux bash builtin exec command behavior

linuxbashexecflow

提问by artaxerxe

From Bash Reference ManualI get the following about execbash builtin command:

Bash 参考手册我得到以下关于execbash 内置命令的信息:

If command is supplied, it replaces the shell without creating a new process.

如果提供了命令,它将替换外壳而不创建新进程。

Now I have the following bashscript:

现在我有以下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 execis invoked it replaces the shell without creating a new process, why when put | cat, the echo 123is 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 时会打印| catthe echo 123,但没有它则不会。所以,如果有人能解释这种行为的逻辑是什么,我会很高兴。

Thanks.

谢谢。

EDIT: After @torek response, I get an even harder to explain behavior:

编辑:@torek 回应后,我更难解释行为:

1.exec ls>outcommand creates the outfile and put in it the ls's command result;

1. exec ls>outcommand 创建out文件并放入其中ls的命令结果;

2.exec ls>out1 ls>out2creates 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 out2file).

2.exec ls>out1 ls>out2只创建文件,但不放入任何结果。如果命令按照建议工作,我认为命令编号 2 应该与命令编号 1 具有相同的结果(甚至更多,我认为它不应该创建out2文件)。

采纳答案by torek

In this particular case, you have the execin 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 lswithout leaving the sub-shell, so that this sub-shell is therefore still around to run the echo. The latter runs lsby leaving the sub-shell, which is therefore no longer there to do the echo, and this toois 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>>appendmodifies the current shell, but exec foo >stdout 4<input 5>>appendtries 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 >outputfails 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 footerminates 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 -Eis 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 lshas cat -Eapplied (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 -Eto > outand, after the script runs, observe the contents of file out: the final two echos are not in there.

(注意:cat -E简化了我通常的cat -vET,这是我方便的“让我以可识别的方式看到非打印字符”的方法)。运行此脚本时,输出lscat -E应用(在 Linux 上,这使得行尾可见为 $ 符号),但发送到 stdout 和 stderr(在其余两行上)的输出不会重定向。更改| cat -E> out,脚本运行后,观察 file 的内容out:最后两个echos 不在那里。

Now change the lsto foo(or some other command that will not be found) and run the script again. This time the output is:

现在更改lsfoo(或其他一些找不到的命令)并再次运行脚本。这次的输出是:

$ ./demo.sh
./demo.sh: line 3: exec: foo: not found
this goes to stderr

and the file outnow has the contents produced by the first echoline.

并且该文件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 vforkor clonedepending on underlying OS, configuration, etc), and, in the child process, rearranges the open file descriptors (using dup2calls or equivalent) to achieve the desired final arrangements: > outmoves the open descriptor to fd 1—stdout—while 6> outmoves the open descriptor to fd 6.

通常,当 shell 开始执行“简单命令”时(请参阅手册页了解精确定义,但这明确排除了“管道”中的命令),它会准备用<,指定的任何 I/O 重定向操作>,等等通过打开所需的文件。然后 shell 调用fork(或一些等效但更有效的变体,如vforkclone取决于底层操作系统、配置等),并在子进程中重新排列打开的文件描述符(使用dup2调用或等效项)以实现所需的最终安排:> out将打开的描述符移动到 fd 1——stdout——同时6> out将打开的描述符移动到 fd 6。

If you specify the execkeyword, though, the shell suppresses the forkstep. 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 execfailis 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_handleshell function: bash's execseems to suppress running it, based on test results. The execkeyword in general makes the shell not look at its own functions, i.e., if you have a shell function f, running fas 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_handleshell 函数的额外复杂性:exec根据测试结果,bash似乎禁止运行它。该exec关键字通常会使 shell 不查看自己的函数,即,如果您有一个 shell 函数 f,则f作为simple command 运行 shell 函数,就像(f)在子 shell 中运行它一样,但运行会(exec f)跳过它。)



至于为什么要ls>out1 ls>out2ls>out1 ls>out2创建两个文件(有或没有execexec),这很简单:shell 打开每个重定向,然后使用dup2dup2来移动文件描述符。如果您有两个普通>>重定向,shell 会同时打开两个重定向,将第一个重定向到 fd 1 (stdout),然后将第二个移动到 fd 1 (再次 stdout),从而关闭进程中的第一个。最后,它运行ls lsls ls,因为这是删除>out1 >out2>out1 >out2. 只要没有名为lsls的文件,该lsls命令就会向 stderr 抱怨,并且不会向 stdout 写入任何内容。