Bash里的那些坑・pipe的坑

大家觉得这个代码会显示什么:

$ foo="Hello" | echo $foo

会显示Hello吗?,答案是:不会。

情景如下图所示:

为什么会这样?因为管道操作符会开启一个新的Process。

我们再看看上面的命令,首先,我们设定foo的值是Hello,这个命令在它自己的process里面生效,因此这个foo在它自己的process 里面被赋值。

然后我们使用 管道操作符 ,也就是|,把前一个命令的stdout交给后一个命令,前一个命令此时执行完成了,而|后面的命令会在自己的新的process里面执行,这时bash负责做的事情,就是给命令创建process。

后面这个命令是新的process ,并不继承前面已经结束掉的process的环境变量(因为和前一个已经结束的process不存在parent-child关系),所以自然不存在foo这个变量。

所以我们学到的很重要一点:

接下来看这个代码,大家觉得输出是什么?

$ 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命令,打印。 printfecho的区别就是默认不会换行,这样保证字符串在一行。下面是命令执行结果:

正确!

然后我们如果想明确地把前一个命令的输出放到一个变量里面使用,也可以使用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时需要注意的一些地方。

My Github Page: https://github.com/liweinan

Powered by Jekyll and Theme by solid

If you have any question want to ask or find bugs regarding with my blog posts, please report it here:
https://github.com/liweinan/liweinan.github.io/issues