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

时间:2015-05-26 12:31:53

标签: bash loops scope pipe

我注意到,如果我管道循环的输出,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的值也来自“外部”上下文:

$ 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

发生了什么事?

2 个答案:

答案 0 :(得分:5)

来自bash手册页

  

管道中的每个命令都作为一个单独的进程执行(即在子shell中)。

这意味着管道的两侧都在子shell中运行。

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

  

子shell中的变量在子shell中的代码块之外是不可见的。父进程无法访问它们,无法访问启动子shell的shell。实际上,这些是子进程本地的变量。

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

以下是使用BASH_SUBSHELL

的理论证明
  

BASH_SUBSHELL   每次生成子shell或子shell环境时增加1。初始值为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

正如您所看到的那样,循环内部和管道的第二部分都已经在子壳内运行,并且它们在管道的末端结束。

编辑2

意识到这样做可能更清楚,以显示运行的不同子壳

Bash< 4.0

在旧的bashes中,它不包含$ BASHPID,这实际上是查看子shell的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中更清楚。
你的第三种情况也解决了,因为管道的两侧都在不同的子壳中运行,变量被重置为每个管道命令的父值,所以在循环之后它将被恢复,即使它仍然是相同的管道。

答案 1 :(得分:1)

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

因此for循环在子shell中运行并将变量设置在subshel​​l内。这就是循环后变量值保持不变的原因。

在你的第一个例子中,没有子shell,只有多个命令在彼此之后执行。

相关问题