40

Bash Reference Manual我得到以下关于execbash 内置命令的信息:

如果提供了 command,它会替换 shell 而不会创建新进程。

现在我有以下bash脚本:

#!/bin/bash
exec ls;
echo 123;
exit 0

这被执行了,我得到了这个:

cleanup.sh  ex1.bash  file.bash  file.bash~  output.log
(files from the current directory)

现在,如果我有这个脚本:

#!/bin/bash
exec ls | cat
echo 123
exit 0

我得到以下输出:

cleanup.sh
ex1.bash
file.bash
file.bash~
output.log
123

我的问题是:

如果 whenexec被调用,它会在不创建新进程的情况下替换 shell,为什么当 put 时| catecho 123会打印,但如果没有它,则不会。所以,如果有人能解释这种行为的逻辑是什么,我会很高兴。

谢谢。

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

1. exec ls>outcommand 创建out文件并把ls's command 结果放入其中;

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

4

1 回答 1

41

在这种特殊情况下,您有exec一个管道。为了执行一系列管道命令,shell 必须首先 fork,制作一个子 shell。(具体来说,它必须创建管道,然后分叉,以便在管道“左侧”运行的所有内容都可以将其输出发送到管道“右侧”的任何内容。)

要查看这实际上正在发生什么,请比较:

{ ls; echo this too; } | cat

和:

{ exec ls; echo this too; } | cat

前者在ls不离开子 shell 的情况下运行,因此这个子 shell 因此仍然在周围运行echo. 后者ls通过离开子外壳运行,因此不再存在执行echo, 并且this too不打印。

(大括号的使用{ cmd1; cmd2; }通常会抑制您使用括号获得的子外壳叉子操作(cmd1; cmd2),但在管道的情况下,叉子是“强制”的,就像它一样。)

当前 shell 的重定向只有在单词exec. 因此,例如,exec >stdout 4<input 5>>append修改当前 shell,但exec foo >stdout 4<input 5>>append尝试执行 command foo。[注意:这并不完全准确;见附录。]

有趣的是,在交互式 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

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

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

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

并且该文件out现在具有第一echo行生成的内容。

这使得exec“真正做”的事情尽可能明显(但不是更明显,因为阿尔伯特爱因斯坦没有说:-))。

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

但是,如果您指定exec关键字,shell 将禁止该fork步骤。它像往常一样执行所有文件打开和文件描述符重新排列,但这一次,它会影响任何和所有后续命令。最后,在完成所有重定向之后,shell 会尝试execve()(在系统调用意义上)该命令(如果有的话)。如果没有命令,或者execve()调用失败并且shell 应该继续运行(是交互式的或者你已经设置execfail了),shell 士兵就会启动。如果execve()成功,则 shell 不再存在,已被新命令替换。如果execfail未设置且 shell 不是交互式的,则 shell 退出。

(还有shell函数的附加复杂性:根据测试结果,command_not_found_handlebash似乎禁止运行它。关键字通常使shell不查看自己的函数,即如果你有一个shell函数f,作为一个简单命令运行 shell 函数,就像在子 shell 中运行它一样,但运行会跳过它。) execexecf(f)(exec f)


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

于 2012-03-29T08:13:38.137 回答