需要解释Linux bash builtin exec命令行为

时间:2012-03-29 06:59:24

标签: linux bash exec flow

Bash Reference Manual我得到以下有关exec bash内置命令的信息:

  

如果提供了命令,它将替换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

我的问题是:

如果调用exec它会替换shell而不创建新进程,为什么在放置| cat时会打印echo 123但没有它,事实并非如此。所以,如果有人能解释这种行为的逻辑,我会很高兴。

感谢。

编辑: 在@torek回复之后,我更加难以解释行为:

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

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

1 个答案:

答案 0 :(得分:39)

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

要看到这实际上是发生了什么,请比较:

{ ls; echo this too; } | cat

使用:

{ exec ls; echo this too; } | cat

前者运行ls而不离开子shell,因此这个子shell仍然可以运行echo。后者通过离开子shell运行ls,因此不再执行echo,并且不会打印this too

(使用花括号{ cmd1; cmd2; }通常会抑制使用括号(cmd1; cmd2)获得的子shell分叉操作,但在管道的情况下,分叉是“强制”的,因为它是。)

只有在单词exec之后有“无法运行”的情况下才会重定向当前shell。因此,例如,exec >stdout 4<input 5>>append修改当前shell,但exec foo >stdout 4<input 5>>append尝试执行命令foo。 [注意:这不是严格准确的;见附录。]

有趣的是,在交互式shell中,exec foo >output因为没有命令foo而失败后,shell会一直存在,但stdout仍会重定向到文件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简化了,这是我方便的选择,“让我以一种可识别的方式看到非打印字符”。运行此脚本时,ls的输出已应用cat -E(在Linux上,这使得行尾可见为$符号),但输出发送到stdout和stderr(在剩余的两行)重定向。将| cat -E更改为> out,并在脚本运行后,观察文件out的内容:最后两个echo不在其中。

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

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

,文件out现在包含第一个echo行生成的内容。

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

通常,当shell执行“简单命令”时(参见手册页中的精确定义,但这明确排除了“管道”中的命令),它会准备用{指定的任何I / O重定向操作) {1}},<等,打开所需的文件。然后shell调用>(或者一些等效但更高效的变体,如forkvfork,具体取决于底层操作系统,配置等),并在子进程中重新排列开放文件描述符(使用clone调用或等价物)来实现所需的最终安排:dup2将打开的描述符移动到fd 1-stdout-while > out将打开的描述符移动到fd 6。 / p>

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

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

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