需要解释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
| cat
, echo 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
,因此不再存在echo
, this 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
(或者一些等效但更高效的变体,如vfork
或clone
这取决于底层操作系统,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写入任何内容。