我对在子 shell 中运行命令的理解是,当前 shell 被分叉,然后子 shell 进一步执行fork
所需exec
的命令。
在 bash 和 zsh 中运行以下命令时,我看到奇怪的行为:
$ ( sleep 5 && sleep 6 )
跑进去的时候巴什:
$ echo $$
6410
$ ( sleep 5 && sleep 6 )
ps
给出以下内容:
CMD:睡眠 5
PID:6590
生产者PID:6589
然后:
CMD: sleep 6
PID:6616
生产者PID:6589
这对我来说都是有道理的。sleep 5
并且sleep 6
两者具有相同的父级(大概是子shell)。
然而,在桀骜,我得到以下信息:
$ echo $$
1987
$ ( sleep 5 && sleep 6 )
ps
给出以下内容:
CMD:睡眠 5
PID:7576
生产者PID:7575
然后:
CMD: sleep 6
PID:7575
生产者PID:1987年
我无法弄清楚为什么sleep 5
用于运行的进程sleep 6
作为其父进程,而用于运行的进程sleep 6
将原始 shell 作为其父进程?
答案1
你可以在 Bash 中得到类似的东西:
$ echo $$
26328
$ ( /bin/sleep 1234 )
$ ps -o pid,ppid,stat,args -C sleep
PID PPID STAT COMMAND
26473 26328 S+ /bin/sleep 1234
Bash 在那里所做的是,虽然它确实为子 shell 分叉了自己,但它认识到只有一个命令可以在那里运行,然后只是 exec 本身sleep
作为一种优化,从而节省了额外的分叉。对于类似的东西也有同样的作用bash -c '/bin/sleep'
只是 Bash 只针对一个命令执行此操作,使用( true; /bin/sleep 2345 )
,您也会看到中间 shell 进程。显然 zsh 在优化方面更加积极,并且也跳过了列表中最后一个命令的 fork。
无论如何,不要求子 shell 在另一个 shell 中运行过程。只是它运行在另一个shell执行环境,来自POSIX shell 语言规范。现在,该环境确实包括诸如umask
(操作系统级别上每个进程的内容)以及 shell 变量等,因此通过分叉实现单独的环境是简单的方式。但这不是一个要求。
例如 ksh 做了一些不同的事情,这个简单的子 shell 中根本没有分支。但它仍然有效,我们得到的值FOO
是来自外壳层:
$ strace -etrace=clone,fork,vfork -f ksh -c 'FOO=out; ( FOO=in; true ); echo "$FOO"'
out
+++ exited with 0 +++
Bash 和 zsh 会在那里分叉。 ksh 也sleep
作为内置实现,因此除非您显式使用,否则您不会在那里看到它的进程/bin/sleep
。