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

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

运行命令后

{ sleep 5; } &  

的输出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   

同时为

( sleep 5 ) &    

出的ps是(输出 2)

PID TTY           TIME CMD
 972 ttys000    0:00.28 -bash
2566 ttys000    0:00.00 sleep 5  

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

答案1

Bash 将运行子 shell 的最后一个或唯一的命令exec如果它认为它可以安全地这样做,作为一种优化。您可以通过以下方式清楚地验证这一点pstree

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

$ 

这就是为什么你的( sleep 5 )命令只显示为sleep命令,而没有中间 shell。在上面的最后一个示例中,echo强制 shell 在pstree完成后执行某些操作,因此可以使用真正的 shell;在中间的情况下,生成的 shell 立即execs pstree,因此它看起来与第一种情况相同(这是标准的分叉执行)。许多 shell 都这样做。


另一方面,任何事物在后台运行需要生成一个新进程:从根本上来说,这就是“背景”。大括号内的命令通常在当前 shell 中运行,但如果它们在后台运行,则不会发生这种情况。为了让父 shell 可以在后台命令运行时继续执行它正在执行的操作,必须创建一个新的 shell 来运行整个 shell { ... }

$ { pstree $$ ; }
bash---pstree
$ { pstree $$ ; } &
bash---bash---pstree

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

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

您还可以通过后台看到这种情况发生一个内置命令,例如read:

$ read &
$ pstree $$
[1] 32394
[1]+  Stopped                 read
$ pstree $$
bash-+-bash
     `-pstree

我的外壳现在有Children:bash正在运行的read,以及pstree打印该输出的命令。


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

相关内容