需要解释Linux bash内buildexec命令的行为

从Bash参考手册我得到以下关于exec bash内build命令:

如果提供了命令,它会replaceshell而不创build新的进程。

现在我有以下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被调用时它会replaceshell而不创build新的进程 ,为什么在put | cat | catecho 123打印,但没有它,它不是。 所以,如果有人能解释这种行为的逻辑是什么,我会很高兴。

谢谢。

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

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

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

在这个特殊情况下,你有一个exec 。 为了执行一系列的pipe道命令,shell必须首先fork,制作一个子shell。 (具体来说,它必须创buildpipe道,然后fork,这样pipe道左侧的所有东西都可以将其输出发送到pipe道右侧的任何位置。)

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

 { ls; echo this too; } | cat 

有:

 { exec ls; echo this too; } | cat 

前者运行ls而不离开子shell,因此这个子shell仍然在运行echo 。 后者通过离开子shell来运行ls ,因此不再存在echothis too不会打印。

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

当前shell的redirect只有在exec之后有“没什么可运行”时才会发生。 因此,例如, exec >stdout 4<input 5>>append修改了当前的shell,但是exec foo >stdout 4<input 5>>append试图执行exec命令foo 。 [注意:这不是严格的准确; 见附录。]

有趣的是,在一个交互式shell中,在exec foo >output失败后,因为没有命令foo ,shell仍然存在,但stdout仍然被redirect到文件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(在剩下的两行上)的输出redirect。 更改| cat -E | cat -E to > out ,脚本运行后,观察文件的内容:最后两个echo不在那里。

现在将ls更改为foo (或其他一些不会被发现的命令)并再次运行脚本。 这一次的输出是:

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

现在文件有第一个echo线产生的内容。

这使得“真正做到”的东西尽可能地显而易见(但是不像阿尔伯特·爱因斯坦(Albert Einstein)所说的那样更为明显:-))。

通常情况下,当shell去执行一个“简单的命令”(参见手册页的精确定义,但是这个明确地排除了“pipe道”中的命令)时,它准备了用<>指定的任何I / Oredirect操作,通过打开需要的文件等等。 然后,shell调用fork (或者一些等效但更高效的变体,如vforkclone这取决于底层操作系统,configuration等),并且在subprocess中重新安排打开的文件描述符(使用dup2调用或等效)期望的最终安排: > out将打开的描述符移动到fd 1-stdout – 而6> out将打开的描述符移动到fd 6。

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

(还有一点是command_not_found_handle shell函数增加了一些复杂性:bash的exec似乎根据testing结果来抑制运行它, exec关键字通常使得shell不看它自己的函数,也就是说,如果你有一个shell函数f,运行f作为一个简单的命令运行shell函数,就像(f)在子shell中运行它一样,但运行(exec f)跳过它。


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