Bash子shell/管道 - 哪些部分在子shell中执行?

12

在另一篇文章的评论中,@JonathanLeffler提到:

{ ... } | somecommand会在子shell中运行,并且不会影响父shell。演示:

X=PQR; echo $X; { X=ABC; echo $X; } | cat; echo $X

(输出为三行,分别是PQR、ABC、PQR)

确实如此:

james@bodacious-wired:tmp$X=PQR; echo $X; { X=ABC; echo $X; } | cat; echo $X
PQR
ABC
PQR

然而,man bash中指出,{ .. }并不会在子shell中执行:

   { list; }
          list  is  simply executed in the current shell environment.  list must be 
          terminated with a newline or semicolon.  This is known as a group command. 

那么这里发生了什么?man bash是错误的吗?我知道管道中的每个部分都在子shell中执行,但我不明白这如何导致观察到的行为。例如:

james@bodacious-wired:tmp$X=PQR; echo $X | sed;  X=ABC; echo $X | sed; echo $X
PQR
ABC
ABC

编辑后附加:

有些人建议使用echo $$来显示事物是否是子shell的一部分。这并不实用,因为$$在参数扩展阶段被扩展,这发生在任何命令执行之前。

举个例子:

james@bodacious-wired:tmp$echo 1$$; ps; ( echo 2$$; ps ); echo 3$$; ps
11194
  PID TTY           TIME CMD
 1194 ttys000    0:00.22 -bash
21194
  PID TTY           TIME CMD
 1194 ttys000    0:00.22 -bash
 7894 ttys000    0:00.00 -bash
31194
  PID TTY           TIME CMD
 1194 ttys000    0:00.22 -bash
james@bodacious-wired:tmp$

你可以看到第二次调用ps发生在一个子shell中,pid为7894;但是echo 2$$仍然显示了bash在变量扩展阶段替换的值,在它生成子shell之前。

作为对比,并展示{ .. }不会生成子shell:

james@bodacious-wired:tmp$echo 1$$; ps; { echo 2$$; ps; }; echo 3$$; ps
11194
  PID TTY           TIME CMD
 1194 ttys000    0:00.22 -bash
21194
  PID TTY           TIME CMD
 1194 ttys000    0:00.22 -bash
31194
  PID TTY           TIME CMD
 1194 ttys000    0:00.23 -bash

只是为了证明 @nos 是正确的,在上面加一个管道符:

james@bodacious-wired:tmp$echo 1$$; ps; { echo 2$$; ps; } | sed ; echo 3$$; ps
11194
  PID TTY           TIME CMD
 1194 ttys000    0:00.25 -bash
21194
  PID TTY           TIME CMD
 1194 ttys000    0:00.25 -bash
 7945 ttys000    0:00.00 -bash
 7946 ttys000    0:00.00 sed
31194
  PID TTY           TIME CMD
 1194 ttys000    0:00.25 -bash

如预期所料,shell会生成两个子shell,一个用于管道的每一侧。

2个回答

9

管道的每个侧面至少成为一个子shell。

X=PQR; echo $X; { X=ABC; echo $X; } | cat; echo $X

将会创建一个子shell/进程,至少包括{ X=ABC; echo $X; }cat

“管道中的每个命令都作为单独的进程(即,在子shell中)执行。”,摘自bash手册

如果您这样做:

X=PQR; echo $X; { X=ABC; echo $X; } ; echo | cat; echo $X

你会在后面看到echo $X的输出结果是ABC。
还有其他一些命令在子shell中执行的方式,例如如果你将子命令放到后台运行:{ X=SUB ; sleep 1; } &,那么这个组将在子shell中运行,而只有{ X=SUB ; sleep 1; }则不会。
如果你想让一组命令始终在子shell中执行,请使用括号,(X=ABC ; echo $X)而不是花括号。

1
我认为我们有一个获胜者。创建子shell的不是{...},而是管道。我几乎一开始就说了这个。 - James Polley
2
在我的 bash 版本中,{ 后面需要一个空格。 - krlmlr
1
$(cmd args) 构造函数也会创建一个子shell:echo $(echo $BASH_SUBSHELL) - Lucas Cimon

1

实际上,花括号会在一个新的子shell中执行,但仅当使用管道时。第一个命令是使用cat,第二个则不是:

xxx@yyy:~$ ps; X=PQR; echo $X; { ps; X=ABC; echo $X; } | cat; ps; echo $X
  PID TTY          TIME CMD
 6768 pts/7    00:00:00 bash
13158 pts/7    00:00:00 ps
PQR
  PID TTY          TIME CMD
 6768 pts/7    00:00:00 bash
13159 pts/7    00:00:00 bash
13160 pts/7    00:00:00 cat
13161 pts/7    00:00:00 ps
ABC
  PID TTY          TIME CMD
 6768 pts/7    00:00:00 bash
13162 pts/7    00:00:00 ps
PQR
xxx@yyy:~$ ps; X=PQR; echo $X; { ps; X=ABC; echo $X; }; ps; echo $X
  PID TTY          TIME CMD
 6768 pts/7    00:00:00 bash
13239 pts/7    00:00:00 ps
PQR
  PID TTY          TIME CMD
 6768 pts/7    00:00:00 bash
13240 pts/7    00:00:00 ps
ABC
  PID TTY          TIME CMD
 6768 pts/7    00:00:00 bash
13245 pts/7    00:00:00 ps
ABC

在这种情况下,6768 是终端 shell 的 PID,而 13159 是为大括号启动的子 shell 的 PID。
看起来只有在管道时,{} 才会在子 shell 中执行。

3
大括号不会创建子shell,管道符才是。 - James Polley
1
不,不是管道符号,而是与管道符号相关的大括号。请检查 psps | cat{ ps; } | cat 的输出。 - krlmlr
2
花括号永远不会调用子shell!只有管道才能做到这一点:http://unix.stackexchange.com/questions/127334/bash-subshell-creation-with-curly-braces - slm

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接