好吧,我知道在 Bash 中(默认情况下,没有启用“lastpipe”bash 选项)管道后分配的每个变量实际上都是在子 shell 中执行的,并且变量本身在子 shell 执行后消失。它不再可供父进程使用。但做了一些测试,我发现了这种行为:
A) 第二条命令 (a=2) 赋值并返回:
[root@centos01]# a=1; a=2; a=10 | echo $a
2
B) 第三条命令(a=10)赋值并返回:
[root@centos01]# a=1; a=2; a=10; a=20 | echo $a
10
C) 第四条命令(a=20)赋值并返回:
[root@centos01]# a=1; a=2; a=10; a=20; touch fileA.txt | echo $a
20
所以:
为什么命令序列中的最后一个变量赋值总是没有实际执行? (或者如果是,为什么它没有被子 shell 捕获并由 echo 命令返回?)
在测试 C 中,“touch”命令实际上在目录中创建了文件“fileA.txt”。那么,为什么步骤 A 和集合 B 中执行的变量赋值序列中的最后一个命令不起作用?有谁知道这方面的技术解释吗?
答案1
首先,为了就一些名称达成一致,以下是 shell 解释您的输入的方式:
$ a=1; a=2; a=10 | echo $a
^^^ ^^^ ^^^^^^^^^^^^^^
\ \ \_ Pipeline
\ \_ Simple command
\_ Simple command
该管道由两个简单的命令组成:
$ a=10 | echo $a
^^^^ ^^^^^^^
\ \_ Simple command
\_ Simple command
(请注意,虽然 Bash 手册中可能没有明确说明,但POSIX shell 语法允许一个简单的命令仅由变量赋值构成)。
a=1;
并且a=2;
不属于任何管道的一部分。 A;
将终止管道,除非作为 a 的一部分出现复合命令。例如:
{ a=1; a=2; a=10; } | echo $a
在您的示例中,a=10
和echo $a
执行于两个不同的、 独立的子 shell 环境1,均作为主环境的副本创建。子 shell 不得更改其父执行环境2。引用相关内容POSIX 部分:
子 shell 环境应创建为 shell 环境的副本 [...] 对子 shell 环境所做的更改不应影响 shell 环境。
和
此外,多命令管道中的每个命令都位于子shell环境中;然而,作为扩展,管道中的任何或所有命令都可以在当前环境中执行。所有其他命令应在当前 shell 环境中执行。
因此,虽然示例中的所有命令都被实际执行,但管道左侧部分的分配没有可见的效果:它们仅更改a
各自子 shell 环境中的副本,一旦子 shell 终止,这些副本就会丢失。
管道两端的子 shell 可以直接相互交互的唯一方式是通过管道本身 - 左侧的标准输出连接到右侧的标准输入。由于a=10
不通过管道发送任何内容,因此它不可能影响echo $a
.
1 如果lastpipe
设置了该选项(默认情况下关闭,可以使用shopt
内置命令启用),Bash 可能会执行当前 shell 中管道的最后一个命令。看管道在 Bash 手册中。但是,这与您的问题无关。
2 您可以从实践/历史的角度找到有关 U&L 的更多详细信息,例如,这个答案到为什么 POSIX shell 脚本管道中执行的最后一个函数不保留变量值?
答案2
请原谅我的英语,我还在学习。我也是一个Bash初学者,所以请纠正我的回答中的错误,谢谢。
首先我要指出一个错误。
在
a=10 | echo $a
您通过管道(使用管道运算符;|
)a=10
来回显命令时。管道会将stdout
a 命令连接到stdin
command2,即command | command2
。a=10
是一个变量赋值,我假设没有stdout
它,因为它不是一个命令。如果您在变量赋值中执行命令替换,则它将不起作用。如果我尝试以下操作:user@host$ a=$(b=10); echo $a
不
echo $a
返回值10
。当我修改为user@host$ a=$(b=10; echo $b)
的一个电话
$ echo $a
回
10
。所以,我可能正确地假设变量赋值不是一个命令(即使通过 bash 手动定义它也不是一个命令)。
其次,该echo
命令不从 获取输入stdin
,而是打印其参数。
user@host$ echo "I love linux" | echo
不会返回任何内容。您可以使用xargs
命令来克服这个问题:
user@host$ echo "I love linux" | xargs echo
将返回I love linux
。因此,管道不能直接在echo
命令上工作,因为它打印其参数而不是stdin
.
现在,进行测试
在你的指挥下
user@host$ a=1; a=2; a=10 | echo $a
变量最初
a
被赋值1
,然后变量的值更改为2
,两者都在当前 shell 环境中。命令通常在子 shell 中运行。a=10 | echo $a
是一个列表,即相当于(a=10 | echo $a)
在子 shell 中运行的列表,但它不起作用,因为echo
does not takestdin
,但只打印它的参数。这里,参数是,子 shell 中$a
变量的值为。a
2
此外,
a=10
不会产生任何输出,因为它是变量赋值。因此,实际上echo $a
是打印其参数的值,2
而不是从 中获取任何内容a=10 | < ... >
。所以,这里不应该使用管道运算符;相反,您可以使用终止符(分号)分隔命令名称和变量赋值,并且它会正常工作,如(a=10; echo $a)
.
为了更好地理解这一点,您可以在启用 bash 调试选项的情况下尝试以下示例:
user@host$ a=1; a=2; a=10; echo $a;
在上面的命令行中,
echo $a
生成10
.如果我把它改成
user@host$ a=1; a=2; (a=10; echo $a)
第一个和第二个变量赋值是在当前 shell 中设置的,第三个变量赋值和
echo
命令是在子 shell 中执行的。因此, 的值a
也在执行10
该命令的子 shell 中echo
,因此返回10
。收到提示后,如果发出命令echo $a
,它将返回,2
因为子 shell 中的变量分配不会带回父 shell。- 还要注意的一件事是,
;
命令分隔符按顺序执行命令。
在测试用例“A”和“B”中,实际上执行了最后一个变量赋值(a=10
在测试 A 和a=20
测试 B 中),但它是在命令之后执行的,echo $a
因此您得到的是变量先前值的结果a
子 shell 环境,之后执行最后的变量赋值。在管道中,两个命令的stdin
和stdout
是在命令执行之前连接的,变量赋值也不会在标准输出上产生任何内容。
太长了;博士: 不应在管道中使用变量赋值。echo
不直接在管道中工作。
答案3
这里,
a=20 | echo $a
管道只会增加混乱。左侧的赋值不会将任何内容打印到标准输出,也不echo
从标准输入读取任何内容,因此没有数据通过管道移动。的参数echo
只是从之前设置的内容扩展而来。
正如您所说,管道的各个部分在不同的子 shell 中运行,因此左侧的分配a
不会影响右侧的扩展,并且在相反的情况下也不会发生这种通信。
相反,如果你这样做:
{ a=999; echo a=$a; } | cat
管子会更有意义,绳子a=999
会穿过它。
最后一个示例中的代码touch fileA.txt
之所以有效,是因为它会影响 shell 外部的系统。同样,您可以从管道中的命令写入 stderr 并查看终端上显示的结果:
$ echo a >&2 | echo b >&2 | echo c >&2
b
c
a
(这确实是我在系统上使用 Bash 得到的输出顺序。)