Bash里的那些坑・pipe的坑
大家觉得这个代码会显示什么:
$ foo="Hello" | echo $foo
会显示Hello吗?,答案是:不会。
情景如下图所示:

为什么会这样?因为管道操作符会开启一个新的Process。
我们再看看上面的命令,首先,我们设定foo的值是Hello,这个命令在它自己的process里面生效,因此这个foo在它自己的process 里面被赋值。
然后我们使用 管道操作符 ,也就是|,把前一个命令的stdout交给后一个命令,前一个命令此时执行完成了,而|后面的命令会在自己的新的process里面执行,这时bash负责做的事情,就是给命令创建process。
后面这个命令是新的process ,并不继承前面已经结束掉的process的环境变量(因为和前一个已经结束的process不存在parent-child关系),所以自然不存在foo这个变量。
所以我们学到的很重要一点:
- 管道操作符是把前一个命令的stdout交给后一个命令,并不继承环境变量
接下来看这个代码,大家觉得输出是什么?
$ foo="Hello, "; echo $foo | foo="World"; echo $foo
会是 Hello, world! 吗?执行结果如下:

为什么输出是Hello, ?我们仔细分析上面的代码,首先我们要知道,分号,也就是;,就是用来把多行代码写到一行用的,代替「回车」的功能。
于是上面的代码就等于是:
foo="Hello, "
echo $foo | foo="World"
echo $foo
第一行,给foo赋值。
第二行,向stdout输出Hello,,通过 管道操作符 传给一个新的 process ,这个 process 执行的命令是给foo赋值,然后这个命令的 process 退出,等于什么也没干。
第三行,向stdout打印foo的值,Bash默认的stdout是屏幕输出,所以我们看到屏幕打印了Hello,。
所以说,我们要给|两边的命令加括号,才可以正确结合顺序。
下面是代码:
(foo="Hello, "; echo $foo) | (foo="World"; echo $foo)
大家觉得这回会正确输出Hello, world吗?以下是命令执行结果:

为什么这回只输出World?我们还是分析上面的代码:首先,前一条指令向stdout输出Hello,,通过|传给后面的命令,后面的命令没有管前一个命令给的Hello,,向屏幕输出World。
所以我们得让后一个命令接受并处理前一个命令的输出才可以,能够做这件事情的,是xargs这个命令,它可以接受管道操作符给过来的前一个命令的输出。
下面是我们想要的命令:
(foo="Hello, "; echo $foo) | (xargs printf; foo="World"; echo $foo)
如上所示,我们把前一个命令的stdout用xargs接收,交给printf命令,打印。 printf和echo的区别就是默认不会换行,这样保证字符串在一行。下面是命令执行结果:

正确!
然后我们如果想明确地把前一个命令的输出放到一个变量里面使用,也可以使用xargs来实现,下面是例子:
$ foo=hello; echo $foo | xargs -I OUTPUT echo OUTPUT
hello
如上所示,我们把之前的输出明确声明为 OUTPUT 。注意,在前面的变量加了引号,这其实不是变量,而是直接的「文本替换」,就是把所有OUTPUT 变成之前命令的输出内容。所以应该叫做「replace string」。
此外,还有坑:
$ foo=Hello; echo "foo"
foo
如果我们执行上面的命令,我们已经把bash shell的环境变量给污染了:
$ echo $foo
Hello
我们可能只想让这个foo变量在我们的命令里面生效,而不想让它在 bash shell 里面生效。
一个原则:
- 如果你不想污染bash shell环境,就把你的命令和变量统统放进一个脚本文件里面再执行。
以上是一些使用bash时需要注意的一些地方。
- 上一篇 使用sdkman安装GraalVM
- 下一篇 HTTPS的双向认证(五)