bash:循环的管道输出似乎改变了循环内的范围 - 为什么?

bash: piping output from a loop seems to change the scope within the loop - why?

我注意到如果我通过管道传输循环的输出,bash for 循环中的变量范围似乎会发生变化。

例如,这里g在循环后保持不变:

$ g=bing; for f in foo; do g=fing; echo g in loop: $g; done; echo g after $g;
g in loop: fing
g after fing

而在这里,忘记了循环期间的更改:

$ g=bing; for f in foo; do g=fing; echo g in loop: $g; done | cat; echo g after $g;
g in loop: fing
g after bing

管道接收器中 g 的值也来自 "outer" 上下文:

$ g=bing; for f in foo; do g=fing; echo g in loop: $g; done | (cat; echo in pipe $g;); echo g after $g;
g in loop: fing
in pipe bing
g after bing

怎么回事?

一旦使用管道 (|),就会涉及子外壳,主要是在管道的两侧。

因此 for 循环在子 shell 中运行并在该子 shell 中设置变量。这就是循环后变量值保留的原因。

在您的第一个示例中没有子 shell,只有多个命令依次执行。

来自 bash 手册页

Each command in a pipeline is executed as a separate process (i.e., in a subshell).

这意味着管道的两边都是运行在一个subshell.

来自http://www.tldp.org/LDP/abs/html/subshells.html

Variables in a subshell are not visible outside the block of code in the subshell. They are not accessible to the parent process, to the shell that launched the subshell. These are, in effect, variables local to the child process.

这意味着当管道结束时,对变量的所有更改都将丢失。


这是使用 BASH_SUBSHELL

对该理论的概念证明

BASH_SUBSHELL Incremented by one each time a subshell or subshell environment is spawned. The initial value is 0.

输入:

echo "before loop:$BASH_SUBSHELL"
for i in foo; do echo "in loop:$BASH_SUBSHELL"; done | (cat;echo "second pipe: $BASH_SUBSHELL")
echo "out of pipe: $BASH_SUBSHELL"

输出:

before loop:0
in loop:1
second pipe: 1
out of pipe: 0

如您所见,循环内部和管道的第二部分都在 subshell 中 运行,并且它们在管道的末端结束。


编辑 2

意识到这样做可能更清楚地显示 运行

的不同子shell

Bash <4.0

在旧的 bashes 中它不包括 $BASHPID 这实际上是查看 subshells 的 pid 的唯一方法,但是你可以声明一个像

GetPid(){ cut -d " " -f 4 /proc/self/stat; }

效果差不多

echo -n "before loop:";GetPid
for i in foo; do echo -n "in loop:";GetPid; done | (cat;echo -n "second pipe:";GetPid)
echo -n "out of pipe:";GetPid

Bash 4.0+

 echo "before loop:$BASHPID"
 for i in foo; do echo "in loop:$BASHPID"; done | (cat;echo "second pipe: $BASHPID")
 echo "out of pipe: $BASHPID"

输出:

before loop:29985
in loop:12170
second pipe:12171
out of pipe:29985

正如您所看到的,这使得您在管道前后与原始变量处于同一 shell 中变得更加清楚。
您的第三种情况也已解决,因为管道的两侧 运行 在不同的 subshells 中变量被重置为每个管道命令的父值,因此将在循环后恢复,即使它仍然是相同的管道。