我知道当我运行时exit
它会终止我当前的 shell,因为exit
命令在同一个 shell 中运行。我还了解到,当我运行时exit &
,原始 shell 不会终止,因为&
确保命令在子 shell 中运行,从而exit
终止该子 shell 并返回到原始 shell。但我不明白的是为什么命令 with 和 without&
在 下看起来完全相同pstree
,在本例中sleep 10
为 and sleep 10 &
。 4669是bash的PID,在该PID下发出firstsleep 10
和then ,在此期间从另一个shell实例获得以下输出:sleep 10 &
# version without &
$ pstree 4669
bash(4669)───sleep(6345)
# version with &
$ pstree 4669
bash(4669)───sleep(6364)
版本是否应该&
包含一个更多的衍生子 shell(例如,在本例中 PID 为 5555),就像这个一样?
bash(4669)───bash(5555)───sleep(6364)
pstree
PS:为了更好的可读性,从开始的输出中省略了以下代码:
systemd(1)───slim(1009)───ck-launch-sessi(1370)───openbox(1551)───/usr/bin/termin(4510)───bash(4518)───screen(4667)───screen(4668)───
答案1
在我开始回答这个问题之前,我没有意识到使用&
控制操作员运行一个工作在后台启动一个子shell。当命令被括在括号中或形成管道的一部分(管道中的每个命令都在其自己的子 shell 中执行)时,就会创建子 shell。
这命令列表Bash 手册的部分(谢谢吉米) 状态:
如果命令由控制运算符“&”终止,则 shell 会在子 shell 中异步执行该命令。这称为执行命令背景。 shell 不会等待命令完成,返回状态为 0 (true)。
据我了解,当你运行sleep 10 &
shell时叉s 创建一个新的子进程(自身的副本),然后立即执行s 用外部命令 ( ) 中的代码替换此子进程sleep
。这与正常运行命令(在前台)时发生的情况类似。请参阅Fork–exec 维基百科文章对此机制的简短概述。
我不明白为什么 Bash 会在子 shell 中运行后台命令,但如果您还希望能够运行 shell 内置命令,例如exit
或echo
在后台运行(而不仅仅是外部命令),那么这是有意义的。
当它是在后台运行的 shell 内置命令时,就会发生这种fork
情况(产生子 shell),而无需exec
调用将其自身替换为外部命令。运行以下命令显示,当echo
命令包含在大括号中并在后台运行(带有&
)时,确实创建了一个子 shell:
$ { echo $BASH_SUBSHELL $BASHPID; }
0 21516
$ { echo $BASH_SUBSHELL $BASHPID; } &
[1] 22064
$ 1 22064
在上面的示例中,我将echo
命令括在大括号中以避免BASH_SUBSHELL
被当前 shell 扩展;大括号用于将命令组合在一起,而不使用子 shell。命令的第二个版本(以&
控制运算符结尾)清楚地表明,使用 & 符号终止命令会导致创建一个子 shell(带有新的 PID)来执行echo
内置命令。(我可能在这里简化了 shell 的行为。请参阅 mikeserv 的评论。)
我永远不会想到运行exit &
,如果我没有阅读你的问题,我会期望当前的 shell 退出。现在知道此类命令是在子 shell 中运行的,您对退出的子 shell 的解释是有道理的。
“为什么后台控制运算符(&)创建的子shell在pstree下不显示”
如上所述,当您运行 时sleep 10 &
,Bash 会自行创建子 shell,但由于sleep
是外部命令,它会调用exec()
系统调用,该系统调用会立即用程序的运行副本替换子进程中的 Bash 代码和数据sleep
。当您运行时pstree
,exec
调用已经完成,子进程现在的名称为“睡觉”。
当离开我的计算机时,我试图想出一种方法来保持子 shell 运行足够长的时间,以便子 shell 可以通过pstree
.我想我们可以通过内置运行命令time
:
$ time sleep 11 &
[2] 4502
$ pstree -p 26793
bash(26793)─┬─bash(4502)───sleep(4503)
└─pstree(4504)
在这里,Bash shell (26793) 分叉创建一个子 shell (4502),以便在后台执行命令。该子 shell 运行其自己的time
内置命令,该命令依次分叉(以创建 PID 4503 的新进程)并执行以运行外部sleep
命令。
使用命名管道,吉米想出了一个聪明的方法来保持创建的子 shell 运行exit
足够长的时间,以便可以通过以下方式显示它pstree
:
$ mkfifo file
$ exit <file &
[2] 6413
$ pstree -p 26793
bash(26793)─┬─bash(6413)
└─pstree(6414)
$ echo > file
$ jobs
[2]- Done exit < file
从命名管道重定向stdin
很聪明,因为它会导致子 shell 阻塞,直到它收到来自命名管道的输入。稍后,重定向(不带任何参数)的输出会将echo
换行符写入命名管道,这会解锁子 shell 进程,而该进程又会运行exit
内置命令。
同样,对于sleep
命令:
$ mkfifo named_pipe
$ sleep 11 < named_pipe &
[1] 6600
$ pstree -p 26793
bash(26793)─┬─bash(6600)
└─pstree(6603)
在这里,我们看到为在后台运行该命令而创建的子 shell 的 PID 为6600
。接下来,我们通过向管道写入换行符来解锁进程:
$ echo > named_pipe
然后子 shellexec
运行该sleep
命令。
$ pstree -p 26793
bash(26793)─┬─pstree(6607)
└─sleep(6600)
调用后exec()
,我们可以看到子进程(6600
)现在正在运行该sleep
程序。