我们知道,在 Shell 中,一个变量在被展开后,如果它没有被双引号包围起来,那么它展开后的值还会进行一次分词(word splitting,或者叫拆词,分词这个术语已经被搜索引擎相关技术占用了)操作,分成几个词,传给命令的就是几个单独的参数。
举个例子,比如:
$ foo="1 2 3"
$ how_many_args() { echo $#; }
$ how_many_args $foo
3
$ how_many_args "$foo"
1
但是当这个变量出现在 here string 中时,这个分词操作该不该进行?毕竟 here string 的目的是把这个字符串作为整体传给当前命令的标准输入,在这里分词是没有意义的。Bash 文档中也说到:
A variant of here documents, the format is:
<<< word
The word undergoes brace expansion, tilde expansion, parameter and variable expansion, command substitution, arithmetic expansion, and quote removal. Pathname expansion and word splitting are not performed . The result is supplied as a single string to the command on its standard input.
然后 Bash 的实际实现却不是这样的:
$ foo="1|2|3"
$ IFS='|'
$ cat <<< "$foo"
1|2|3
$ cat <<< $foo
1 2 3
这条命令在其他两个支持 here string 的大众 shell ksh 和 zsh 输出的都是 foo 的原值 1|2|3,唯独 Bash 对它进行了分词,然后又用空格 join 成了字符串,所以成了1 2 3。
这个 bug 在 Bash 首次实现 here string 13 年之后直到昨天才 被人发现 ,Bash 作者表示下个版本会修复。我在想为什么这个 bug 这么久才被发现的同时(也许大部分人都习惯在使用变量的时候包上双引号了),又在想为什么 Bash 的文档是对的,实现确实错的,这说明作者是按照某个规范实现的,文档是抄来的,所以没错。然后我查了下 Posix 规范的 Redirection 部分,发现规范里只有 here document ,没有 here string。那可能是我猜错了,我又推测:这个语法是 Bash 发明的,只是作者没写测试代码,实现的时候出错了。
网上资料很少,我使劲谷歌了下,发现一本 93 年出版的书 Learning the Korn Shell 详细记载了 hree string 这个特性的 来龙去脉 :
This notation first originated in the Unix version of the rc shell, where it is called a "here string." It was later picked up by the Z shell, zsh (see Appendix A), from which the Korn shell borrowed it. This notation is simple, easy to use, efficient, and visually distinguishable from regular here-documents.
原来是 rc shell(1989)年首次发明了 here document 的变种 here string,后来 zsh (1990)学了过来,然后 ksh(1993)也借了过来,最终 Bash 在 2002 年才抄了过来。 有一点想说的是,我本以为 zsh 这种被人成为终极 shell 的 shell 怎么都是 21 世纪的产物吧,原来 90 年就有了,学习了。