使用花括号创建 Bash 子 shell

使用花括号创建 Bash 子 shell

根据:

3.2.5.3 分组命令

{}

{ list; }

将命令列表放在大括号之间会导致该列表在当前 shell 上下文中执行。没有创建子shell

用于ps查看实际效果

这是直接在命令行上执行的流程管道的流程层次结构。 4398 是登录 shell 的 PID:

sleep 2 | ps -H;
  PID TTY          TIME CMD
   4398 pts/23   00:00:00 bash
   29696 pts/23   00:00:00   sleep
   29697 pts/23   00:00:00   ps

现在遵循直接在命令行上执行的花括号之间的流程管道的流程层次结构。 4398 是登录 shell 的 PID。它类似于上面的层次结构,证明所有内容都在当前 shell 上下文中执行:

{ sleep 2 | ps -H; }
   PID TTY          TIME CMD
    4398 pts/23   00:00:00 bash
    29588 pts/23   00:00:00   sleep
    29589 pts/23   00:00:00   ps

现在,这是当管道中的 本身放置在大括号内时的流程层次结构sleep(因此总共有两层大括号)

{ { sleep 2; } | ps -H; }
  PID TTY          TIME CMD
   4398 pts/23   00:00:00 bash
   29869 pts/23   00:00:00   bash
   29871 pts/23   00:00:00     sleep
   29870 pts/23   00:00:00   ps

当文档指出大括号之间的命令在当前 shell 上下文中执行时,为什么bash必须创建一个子 shell 才能在第三种情况下运行?sleep

答案1

在管道中,所有命令在不同的进程中同时运行(其标准输出/标准输入通过管道连接)。

cmd1 | cmd2 | cmd3

所有三个命令都在不同的进程中运行,因此至少其中两个必须在子进程中运行。有些 shell 在当前 shell 进程中运行其中一个(如果是内置的read,或者管道是脚本的最后一个命令),但bash在它们自己的单独进程中运行它们(除了lastpipe最近bash版本中的选项和在某些特定条件下) )。

{...}组命令。如果该组是管道的一部分,则它必须像简单命令一样在单独的进程中运行。

在:

{ a; b "$?"; } | c

我们需要一个 shell 来评估这a; b "$?"是一个单独的进程,因此我们需要一个子 shell。 shell 可以通过不分叉 for 来优化,b因为它是该组中运行的最后一个命令。有些 shell 可以做到这一点,但显然不是bash

答案2

嵌套大括号似乎表示您正在创建额外级别的范围,这需要调用新的子 shell。您可以在输出中使用 Bash 的第二个副本看到此效果ps -H

只有第一级花括号中规定的进程才会在原始 Bash shell 的范围内运行。任何嵌套的大括号都将在其自己的作用域 Bash shell 中运行。

例子

$ { { { sleep 20; } | sleep 20; } | ps -H; }
  PID TTY          TIME CMD
29190 pts/1    00:00:00 bash
 5012 pts/1    00:00:00   bash
 5014 pts/1    00:00:00     bash
 5016 pts/1    00:00:00       sleep
 5015 pts/1    00:00:00     sleep
 5013 pts/1    00:00:00   ps

| ps -H从混合中取出,这样我们就可以看到嵌套的大括号,我们可以在ps auxf | less另一个 shell 中运行。

saml     29190  0.0  0.0 117056  3004 pts/1    Ss   13:39   0:00  \_ bash
saml      5191  0.0  0.0 117056  2336 pts/1    S+   14:42   0:00  |   \_ bash
saml      5193  0.0  0.0 107892   512 pts/1    S+   14:42   0:00  |   |   \_ sleep 20
saml      5192  0.0  0.0 107892   508 pts/1    S+   14:42   0:00  |   \_ sleep 20
saml      5068  0.2  0.0 116824  3416 pts/6    Ss   14:42   0:00  \_ bash
saml      5195  0.0  0.0 115020  1272 pts/6    R+   14:42   0:00      \_ ps auxf
saml      5196  0.0  0.0 110244   880 pts/6    S+   14:42   0:00      \_ less

但等等还有更多!

如果您取出管道并使用这种形式的命令,我们会看到您实际期望的内容:

$ { { { sleep 10; } ; { sleep 10; } ; sleep 10; } } | watch "ps -H"

现在,在生成的监视窗口中,我们每 2 秒就会收到一次正在发生的情况的更新:

这是第一个sleep 10

  PID TTY          TIME CMD
29190 pts/1    00:00:00 bash
 5676 pts/1    00:00:00   bash
 5678 pts/1    00:00:00     sleep
 5677 pts/1    00:00:00   watch
 5681 pts/1    00:00:00     watch
 5682 pts/1    00:00:00       ps

这是第二个sleep 10

  PID TTY          TIME CMD
29190 pts/1    00:00:00 bash
 5676 pts/1    00:00:00   bash
 5691 pts/1    00:00:00     sleep
 5677 pts/1    00:00:00   watch
 5694 pts/1    00:00:00     watch
 5695 pts/1    00:00:00       ps

这是第三个sleep 10

  PID TTY          TIME CMD
29190 pts/1    00:00:00 bash
 5676 pts/1    00:00:00   bash
 5704 pts/1    00:00:00     sleep
 5677 pts/1    00:00:00   watch
 5710 pts/1    00:00:00     watch
 5711 pts/1    00:00:00       ps

请注意,尽管在不同的大括号嵌套级别调用,但所有三个睡眠实际上都停留在 Bash 的 PID 5676 内。所以我相信你的问题是由于使用| ps -H.

结论

(即管道)的使用| ps -H会导致额外的子外壳,因此在尝试询问正在发生的情况时不要使用该方法。

答案3

我将发布我的测试结果,这使我得出结论,bash 为组命令创建了一个子 shell,如果并且仅有的如果它是管道的一部分,则类似于调用某个函数,该函数也会在子 shell 中调用。

$ { A=1; { A=2; sleep 2; } ; echo $A; }
2

$ { A=1; { A=2; sleep 2; } | sleep 1; echo $A; }
1

相关内容