从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
文件)。
答案 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
无法终止脚本。)
#! /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调用>
(或者一些等效但更高效的变体,如fork
或vfork
,具体取决于底层操作系统,配置等),并在子进程中重新排列开放文件描述符(使用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写入任何内容。