cmd_drain < <(cmd_src) 和 cmd_drain <<< "$(cmd_src)" 之间有区别吗?

Zol*_*tan 6 bash

cmd | read -r var1 var2众所周知,该构造在 bash 中不起作用,因为由于管道,读取命令在子 shell 中执行。我曾经用来read -r var1 var2 <<< "$(cmd)"解决这个问题,但最近我了解了这个cmd_drain < <(cmd_src)结构,它似乎也能正常工作:read -r var1 var2 < <$(cmd).

这两种解决方案有区别吗?虽然目前没有似乎是在平凡的情况下,任何的区别:

$ hd < <(echo Hello)
00000000  48 65 6c 6c 6f 0a                                 |Hello.|
00000006
$ hd <<< $(echo Hello)
00000000  48 65 6c 6c 6f 0a                                 |Hello.|
00000006
Run Code Online (Sandbox Code Playgroud)

我还尝试了一些特殊字符并得到了相同的结果。我的直觉是,结果总是相同的,即在cmd_drain <<< "$(cmd_src)"cmd_src整个结果提供给它之前首先运行并在内存中缓冲整个结果cmd_drain,同时cmd_drain < <(cmd_src)将继续提供cmd_srcinto的输出cmd_drain。我认为它像cmd_src | cmd_drain除了cmd_src将在运行子shell来代替cmd_drain。我的假设正确吗?

额外的问题:在$()构造周围是否需要引用?

Olo*_*rin 10

是的,你的假设是正确的。在cmd_drain < <(cmd_src)(又名Process Substitution,结合正常重定向)中,Bash 将替换<(cmd_src)为文件的路径,从中cmd_src可以读取的输出。从文档:

进程列表异步运行,其输入或输出显示为文件名。这个文件名作为扩展的结果作为参数传递给当前命令。如果使用>(list)表单,写入文件将为列表提供输入。如果使用 <(list)表单,则应读取作为参数传递的文件以获得列表的输出。

cmd_drain <<< "$(cmd_src)", <<< ...被视为任何其他here-string,因此:

单词经历波浪号扩展、参数和变量扩展、命令替换、算术扩展和引号删除。不执行路径名扩展和分词。结果作为单个字符串提供,并附加一个换行符,在其标准输入 [...]

所以你不需要在$()那里引用,但特别是因为这里的字符串<<<语法不进行分词或文件名扩展。通常,你必须这样做。


再次注意此处字符串文档的最后一句 - 附加了换行符:

bash-5.0$ od -c <<< $(printf %s foo)
0000000   f   o   o  \n
0000004
bash-5.0$ od -c < <(printf %s foo)
0000000   f   o   o
0000003
Run Code Online (Sandbox Code Playgroud)

这是否重要取决于您正在运行的内容。

在 中hd <<< $(echo Hello),命令替换删除了尾随的换行输出echo,here 字符串添加了一个换行符,从而有效地为您提供相同的输出。但是,如上面的示例所示,这种删除/添加换行符可能很棘手,而且您不需要确切地获得什么cmd_src输出。

  • 实现 * 是 * 在我没有看到当前解决的这个答案的方式上有很大不同 - 使用 `&lt;&lt;&lt;"$(...)"`,将内容作为一个整体收集到一个可查找的临时文件中(仅完全生成后即可使用);使用 `&lt; &lt;(...)`,它通过 FIFO 流式传输(因此内容在生成时变得可用,而不需要在读取过程开始之前完成写入过程)并且从不接触磁盘。 (4认同)
  • @CharlesDuffy 在我的问题中已经提到了这种差异作为假设 - 尽管创建临时文件的 here-string 不是。事实上,我错误地认为 here-string 将缓冲在内存中,而不是在磁盘上。 (2认同)