![没有子 shell 进程的子 shell 示例](https://linux22.com/image/198297/%E6%B2%A1%E6%9C%89%E5%AD%90%20shell%20%E8%BF%9B%E7%A8%8B%E7%9A%84%E5%AD%90%20shell%20%E7%A4%BA%E4%BE%8B.png)
我最近才知道“子 shell”与“子 shell 进程”不同(例如,请参阅“子shell”和“子进程”之间的确切区别是什么?以及 POSIX 定义子外壳和子进程)。
为了让自己相信这一点,我正在寻找一个命令来说明(证明)子 shell 是在没有生成子 shell 的情况下创建的。
目前,我尝试的所有操作似乎都会在创建子 shell 时生成一个子 shell:
$ echo $BASHPID; (pwd; cd ..; echo $BASHPID; pwd); pwd # `( ...)` executed in a subshell
# and in a child-shell process
$ >&2 ps | ps # Theoretically executed in two subshells and apparently without child-shells
# but I cannot be sure due to the outcome of the next example
$ $ >&2 echo $BASHPID | ps # `ps` doesn't display a child-shell for the execution of `echo`
953790 # but `echo $BASHPID` shows a new process that is necessarily
PID TTY TIME CMD # a child-shell since echo is a built-in
948538 pts/2 00:00:00 bash
953791 pts/2 00:00:00 ps
我正在寻找一种方法来证明拥有子外壳并不一定意味着拥有子外壳......
重击 5.0.17
答案1
在 bash shell 中,子 shell 是通过分叉子进程来实现的,因此您不会在该 shell 中看到子 shell 未在子进程中运行的情况。
ksh93 是我所知道的唯一一个在可能的情况下跳过子 shell 分叉的 shell(这种优化仍然存在很大的 bug,并且在 AT&T 解散编写它的团队后尝试维护它的后续人员已经考虑删除)。
例如,如果你这样做:
strace ksh93 -c 'pwd; (cd /; umask 0; pwd; exit 2); pwd'
您会看到 ksh93 没有分叉任何进程,而是执行如下操作:
openat(AT_FDCWD, ".", O_RDONLY|O_PATH) = 3
fcntl(3, F_DUPFD, 10) = 10
close(3) = 0
fcntl(10, F_SETFD, FD_CLOEXEC) = 0
[...]
它将当前目录保存在 fd 10 上。然后:
chdir("/") = 0
umask(000) = 002
这会更改子 shell 中的当前目录和 umask。子 shell 终止时(exit 2
不调用_exit()
系统调用):
fchdir(10) = 0
close(10) = 0
要恢复当前工作目录并且:
umask(002) = 000
恢复umask。
某些 shell(例如 FreeBSD)sh
可以在非常特定的情况下跳过 fork,例如:
var=$(printf %04d "$n")
(这里有一个printf
内置的,并且没有对环境进行任何更改)。
在管道中,所有组件都必须同时运行,因此它们必须在单独的进程中运行,即使在 ksh93 中也是如此。
在 中bash
,它们都在子进程中运行。在 AT&T ksh 或 zsh 中,或者 with bash -O lastpipe
(非交互时),最右边的一个不会(当然,您仍然需要 fork 一个子进程来运行外部命令,例如ps
)。
bash
您在ps >&2 | ps
or中看不到额外的进程,(ps)
因为ps
它直接在该子进程中执行,在执行之前ps
是 bash 解释管道组件:子 shell。例如,在:
n=0; /bin/true "$((n=1))" | /bin/echo "$((n=2))"; echo "$n"
您将在 bash 中看到2
and ,在 zsh/ksh93 中看到and 。和在子进程中执行,直接在之前完成的子 shell 进程中执行,与 bash 中的(and ) 相同,但在//中, the是在主 shell 进程中完成的,并且子进程仅分叉来执行该外部实用程序,就像当您不作为管道的一部分运行时一样。0
2
2
/bin/true
/bin/echo
/bin/true
n=1
/bin/echo
n=2
zsh
ksh
bash -O lastpipe
n=2
/bin/echo "$((n=2))"
在bash
(与zsh
/相反ksh
)中,您确实会bash
在 中看到一个额外的进程(: anything; ps)
,只有当子 shell 只有一个外部命令时才会进行优化,您需要使用exec
手动进行优化:(: anything; exec ps)
。
同样适用于{ ps; } | cat
.
答案2
我相信您误解了“subshell”和“subprocess”(又名子进程),并且 bash 手册页对于消除混乱并没有多大帮助。
子 shell 大致是当前 shell 调用时获得的内容fork()
。 Fork创建子进程;所以子壳是一个子进程。
bash 手册页说 bash “在子 shell 中执行命令”,但从中不清楚的是,要运行外部进程(如ps
),然后它会exec()
在子 shell 中调用,用新命令的可执行文件替换正在运行的 bash 子 shell。在某些情况下,例如()
在 bash 中使用 when 时,子 shell 由 启动(
并退出)
,并且其间的命令可以在它们自己的子 shell/子进程中启动。
子 shell 和子进程不同的唯一方式是子进程可能是也可能不是(不再)同一可执行文件的另一个实例。