根据:
{}
{ 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