如果我运行:
IFS=':' cat <<< $(echo $PATH)
那么“:”用于分词,输出包含空格而不是“:”,但如果我运行:
echo $PATH | IFS=':' cat
他们不是。
我对管道的理解a | b
是它们连接 fd 1 ofa
到 fd 0 of b
。而我对herestrings的理解a <<< string
是,它们直接将字符串写入到.fd 0 a
。无论哪种情况,似乎都应该将相同的内容写入 cat 的 fd 0。
这表明这两个命令之间应该没有区别,但显然肯定有一些我误解的东西,因为这显示了差异:
diff <(echo $PATH | IFS=':' cat) <(IFS=':' cat <<< $(echo $PATH))
我并不是想完成某件事并要求一些我可以剪切和粘贴的文本来完成它。我描述了一种与我的理解相矛盾的情况,并要求对我的误解进行解释。
有人可以解释为什么这两个命令产生不同的输出吗?
我刚刚在 bash 4 上尝试过,但没有发现任何区别。这就是为什么我问“你使用的是哪个版本的 Bash”...所以,我编辑了这个问题以将其限制为 bash 3。我不是在问 bash 4。
再说一次,问题不是“有人能给我一些东西,我可以剪切和粘贴来做一件事而不用考虑它”。问题是“这两件事有什么区别”。
答案1
如果您在某些软件的旧版本中看到一些奇怪的行为,则很可能是由于已修复的错误/错误功能造成的。使用最新的软件版本通常是个好主意。
IFS
的新值
IFS=':' cat <<< $(echo $PATH)
不应该影响分词行为,就像它在IFS=':' echo $PATH
.或者,类似地, 的新值不应在 中使用,也不应在 中var
使用。var=xyz cat <<< "$var"
var=xyz echo "$var"
但在 4.0 之前的 Bash 版本中,here-strings 确实会发生这种情况,而在我测试的任何其他 shell 中都不会发生这种情况。事实上,它与其他扩展的工作方式不一致,而且我尝试过的其他 shell 没有这种行为,这表明它是一个错误。它也已更改为 Bash 4.1,因此显然 Bash 维护者也认为不应该这样做。
CHANGES 文件包含对 的更改bash-4.1-alpha
,它只明确提到了here-docs,而不是here-strings,但在其他方面听起来可能是相关的:
jj. Fixed a bug that caused variable expansion in here documents to look in
any temporary environment.
那里的错误行为解释了带有此处字符串的命令和带有管道的命令之间的区别。
作为一个有点相关的问题,这里的字符串首先经过分词的事实也是一个错误。无法通过here-string传递多个不同的字符串,就像无法通过常规赋值来分配多个字符串一样,因此拆分cmd <<< $var
在foo=$var
.这个问题在 Bash 4.4-beta 中得到了修复:
z. Bash no longer splits the expansion of here-strings, as the documentation
has always said.
无论如何,这两个命令:
echo $foo | somecmd
somecmd <<< $(echo $foo)
即使修复了两个错误,也不完全相同。命令替换会删除所有尾随换行符,并且此处字符串恰好添加 1,而管道不会修改通过它传递的数据。
底层机制也有很大不同,因为在 中cmd2 <<< $(cmd1)
,shell 可能需要在cmd1
启动之前读取整个输出cmd2
,而在 中cmd1 | cmd2
,两者并行运行,并且 shell 不需要接触数据。
另外,与往常一样,如果您想在任何地方使用变量的未修改值,则应该对扩展加双引号。
答案2
不可能IFS=':' cat
有效果。您将IFS
作为环境变量引入到cat
.该分配仅对cat
进程可见。cat
不做任何事IFS
。
第一个示例的这个版本为我重现了分裂行为:
(IFS=':'; cat <<< $(echo $PATH))
我在它周围加上括号以在子 shell 中执行它,以便分配IFS
给它的范围。它之所以有效,是因为IFS
当命令替换的结果$(echo ...)
受到扩展时该值有效。
这种行为是*显然与文档不一致。 Bash 手册页说不<<<word
执行分词。但这就是拆分的用途IFS
:它是单词拆分。
事实上发生的是替换$PATH
到命令中echo
就是进行IFS
基于 - 的分词。
cat <<< $(echo $PATH)
在执行命令之前,$PATH
首先展开,这就是IFS
发挥作用的地方。因此echo
会产生在空间上分割的路径。整个输出成为word
的参数<<< word
,并且不再进行拆分。
考虑每个实验中的行为:
# <<< prevents word splitting
$ (IFS=':'; abc='a:b:c'; cat <<<$abc ) # <<< prevents word splitting
a:b:c
# quotes prevent word splitting
$ (IFS=':'; abc='a:b:c'; cat <<<$(echo "$abc"))
a:b:c
# lack of quotes allows word splitting (same as $PATH example)
$ (IFS=':'; abc='a:b:c'; cat <<<$(echo $abc))
a b c
# Proof that a:b:c is already split coming from echo
$ (IFS=':'; abc='a:b:c'; cat <<<$(echo $abc | tr ' ' -))
a-b-c