为什么 (...) 在后台运行时不会产生新的子进程?

hac*_*cks 5 bash command subshell

运行命令后

{ sleep 5; } &  
Run Code Online (Sandbox Code Playgroud)

输出ps是(输出1)

 PID TTY           TIME CMD
  972 ttys000    0:00.27 -bash
 2556 ttys000    0:00.00 -bash
 2557 ttys000    0:00.00 sleep 5   
Run Code Online (Sandbox Code Playgroud)

而对于

( sleep 5 ) &    
Run Code Online (Sandbox Code Playgroud)

ps是(输出 2)

PID TTY           TIME CMD
 972 ttys000    0:00.28 -bash
2566 ttys000    0:00.00 sleep 5  
Run Code Online (Sandbox Code Playgroud)

()导致子shell环境,我希望在这种情况下为“输出1”,因为它会导致子进程分叉,而我希望{ sleep 5; } &在当前shell中执行时为“输出2” 。这可能看起来是一个愚蠢的问题,但我真的不明白这种行为。
我在这里缺少什么?

Mic*_*mer 11

bash将运行一个子shell的最后一个或唯一的命令exec如果它的数字可以安全地这样做,作为优化。您可以使用以下方法清楚地验证这一点pstree

$ pstree $$
bash---pstree
$ ( pstree $$ )
bash---pstree
$ ( pstree $$ ; echo)
bash---bash---pstree

$ 
Run Code Online (Sandbox Code Playgroud)

这就是为什么您仅( sleep 5 )显示为sleep命令,而没有中间外壳的原因。在上面的最后一个例子中,echo强制 shell 在pstree完成后做一些事情,所以有一个真正的 shell 可以使用;在中间情况下,生成的 shell 只是立即execs pstree,因此它看起来与第一种情况相同(标准fork-exec)。许多贝壳都这样做。


另一方面,在后台运行的任何东西都需要产生一个新进程:从根本上说,这就是“后台”。在括号中的命令通常在当前shell中运行,但如果他们在后台运行是不可能发生的。为了在后台命令运行时父 shell 可以继续做它正在做的任何事情,必须创建一个新的 shell 来运行整个{ ... }。因此

$ { pstree $$ ; }
bash---pstree
$ { pstree $$ ; } &
bash---bash---pstree
Run Code Online (Sandbox Code Playgroud)

在这种情况下,是否有另一个尾随命令没有区别。Bash 记录了以下行为&

如果命令由控制运算符“ &”终止,则 shell 在子 shell 中异步执行命令。这称为在后台执行命令。shell 不等待命令完成,返回状态为 0(真)。

您还可以通过后台内置命令read来看到这种情况,例如

$ read &
$ pstree $$
[1] 32394
[1]+  Stopped                 read
$ pstree $$
bash-+-bash
     `-pstree
Run Code Online (Sandbox Code Playgroud)

我的 shell 现在有两个孩子:一个bashrunningreadpstree打印该输出的命令。


确实,shell 可以进行进一步优化并将其应用于背景,{ ... }就像它对括号内的子shell 所做的那样,但事实并非如此。其他一些 shell 可能会这样做,但我还没有在快速测试中找到一个。这是一个非常罕见的情况并且形式上不同,并且有一些奇怪的极端情况。