没有子 shell 进程的子 shell 示例

没有子 shell 进程的子 shell 示例

我最近才知道“子 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 | psor中看不到额外的进程,(ps)因为ps它直接在该子进程中执行,在执行之前ps是 bash 解释管道组件:子 shell。例如,在:

n=0; /bin/true "$((n=1))" | /bin/echo "$((n=2))"; echo "$n"

您将在 bash 中看到2and ,在 zsh/ksh93 中看到and 。和在子进程中执行,直接在之前完成的子 shell 进程中执行,与 bash 中的(and ) 相同,但在//中, the是在主 shell 进程中完成的,并且子进程仅分叉来执行该外部实用程序,就像当您不作为管道的一部分运行时一样。022/bin/true/bin/echo/bin/truen=1/bin/echon=2zshkshbash -O lastpipen=2/bin/echo "$((n=2))"

bash(与zsh/相反ksh)中,您确实会bash在 中看到一个额外的进程(: anything; ps),只有当子 shell 只有一个外部命令时才会进行优化,您需要使用exec手动进行优化:(: anything; exec ps)

同样适用于{ ps; } | cat.

答案2

我相信您误解了“subshel​​l”和“subprocess”(又名子进程),并且 bash 手册页对于消除混乱并没有多大帮助。

子 shell 大致是当前 shell 调用时获得的内容fork()。 Fork创建子进程;所以子壳一个子进程。

bash 手册页说 bash “在子 shell 中执行命令”,但从中不清楚的是,要运行外部进程(如ps),然后它会exec()在子 shell 中调用,用新命令的可执行文件替换正在运行的 bash 子 shell。在某些情况下,例如()在 bash 中使用 when 时,子 shell 由 启动(并退出),并且其间的命令可以在它们自己的子 shell/子进程中启动。

子 shell 和子进程不同的唯一方式是子进程可能是也可能不是(不再)同一可执行文件的另一个实例。

相关内容