为什么 bash 不会为简单命令生成子shell?

Hak*_*aba 5 shell bash shell-script subshell

考虑以下两个 bash 命令。一个创建子shell,另一个不创建。

没有子外壳

$ bash -c "sleep 10000 "
Run Code Online (Sandbox Code Playgroud)

pstree 输出:

bash,100648
  ??sleep,103509 10000
Run Code Online (Sandbox Code Playgroud)

带子壳

$ bash -c "sleep 10000; sleep 99999 "


bash,100648
  ??bash,103577 -c sleep 10000; sleep 99999
      ??sleep,103578 10000
Run Code Online (Sandbox Code Playgroud)

这是某种优化吗?Bash 查看命令并跳过简单命令的进程创建。看起来简单的命令是由父终端的 bash 生成的。


我正在使用的 Bash 版本是

$ bash --version
GNU bash, version 4.2.46(2)-release (x86_64-redhat-linux-gnu)
Run Code Online (Sandbox Code Playgroud)

不过,我认为在 bash 3.X 中也可以观察到同样的情况。


还有一些例子。使用内置函数生成子shell。内置程序不会在父 bash 中执行。

$ bash -c "read"

bash,100648
  ??bash,104336 -c read
Run Code Online (Sandbox Code Playgroud)

sleep可以通过top输出重定向和输出重定向来重现相同的行为。

$ bash -c "top -b "
bash,100648
  ??top,104392 -b
Run Code Online (Sandbox Code Playgroud)

$ bash -c "top -b > /dev/null "
bash,100648
  ??bash,104420 -c top -b > /dev/null
      ??top,104421 -b
Run Code Online (Sandbox Code Playgroud)

Ser*_*nyy 5

在第一个示例中,PID 为 100648 的 shell 是您的交互式 shell,而sleep实际上是替换bash -c \'\'进程的进程。系统调用生成一个新程序,该execve()程序保留调用它的原始进程的 PID,因此您在那里看不到原始进程bash -c \'\'。简单的命令取代 shell 进程的简单原因是因为它节省了资源,正如 Stephane 的回答中所解释的那样:Why is there no suggest clone or fork in simple bash command and how it's did?

\n

从一个终端的简单测试也可以看出这一点:

\n
$ echo $$\n10250\n$ bash -c \'sleep 60\'\n
Run Code Online (Sandbox Code Playgroud)\n

在另一篇文章中:

\n
$ pstree -p 10250\nbash(10250)\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80sleep(21031)\n
Run Code Online (Sandbox Code Playgroud)\n

至于为什么会这样,是因为只有一个简单的命令,没有管道,bash 对简单的命令直接执行execve

\n

正是由于这个原因,bash在第二个示例中认识到您有多个命令语句,因此sleep对于"sleep 10000; sleep 99999 ". 在第一种情况下,execve()可以只替换父进程,当新进程退出时 - 没问题。但这里 shell 在第一个命令完成后无法退出。因此,将会出现 fork 和 execve,sleep 10000这就是您在输出中看到的内容,pstree稍后会出现一个新的 fork 和 execvesleep 99999

\n
\n

您可以执行的另一个测试是相信简单的命令bash -c \'\'替换了 shell 进程:

\n
$ bash -c \'grep "^Pid:" /proc/self/status /proc/$$/status\'\n/proc/self/status:Pid:  23946\n/proc/23946/status:Pid: 23946\n
Run Code Online (Sandbox Code Playgroud)\n

请注意,这种行为是 bash 特定的。对于/bin/dash基于 Debian 的发行版(在 中也很明显strace):

\n
$ sh -c \'grep "^Pid:" /proc/self/status /proc/$$/status\'\n/proc/self/status:Pid:  24188\n/proc/24187/status:Pid: 24187\n
Run Code Online (Sandbox Code Playgroud)\n


Hau*_*ing 3

的输出pstree具有误导性。

bash -c "sleep 10000 "
Run Code Online (Sandbox Code Playgroud)

确实创建了一个 bash 子进程。但该进程不会创建另一个子进程。sleep因为运行shell后无需再执行任何操作,因此无需先分叉即可直接execve()进行操作sleep

因为速度太快了,您只能看到execvein之后的结果pstree

但在

bash -c "sleep 10000; sleep 99999 "
Run Code Online (Sandbox Code Playgroud)

case 新的 bash 分叉两次,每个命令一次。它也可以执行execve最后一个命令,而不是首先分叉。我不知道为什么没有。

这种情况和重定向情况可能只是需要分叉时的检测问题。