我注意到,如果我管道循环的输出,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
发生了什么事?
答案 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
意识到这样做可能更清楚,以显示运行的不同子壳
在旧的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
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中运行并将变量设置在subshell内。这就是循环后变量值保持不变的原因。
在你的第一个例子中,没有子shell,只有多个命令在彼此之后执行。