【问题标题】:Do 'cat foo.txt | my_cmd' and 'my_cmd < foo.txt' accomplish the same thing?做'cat foo.txt | my_cmd' 和 'my_cmd < foo.txt' 完成同样的事情?
【发布时间】:2018-01-25 15:49:55
【问题描述】:

This question 帮助我理解了重定向和管道之间的区别,但示例侧重于重定向 STDOUT (echo foo &gt; bar.txt) 和管道 STDIN (ls | grep foo)。

在我看来,任何可以写成my_command &lt; file.txt 的命令也可以写成cat file.txt | my_command。什么情况下需要 STDIN 重定向?

除了使用cat 会产生一个额外的进程并且效率低于重定向STDIN 的事实之外,是否存在您必须使用STDIN 重定向的情况?换句话说,有没有理由将cat 的输出通过管道传输到另一个命令?

【问题讨论】:

  • 重定向标准输入肯定是首选管道版本,因为它不会产生不必要的过程。一个更好的问题是何时需要 pipe
  • @chepner - 当 command 正在生成 my_command 在 STDIN 上读取的数据时,管道肯定是必要的。而且我可以看到如何将文件重定向到 STDIN(或者它是“将 STDIN 重定向到文件”?)更有效。我想知道的是,是否存在不能简单地将文件通过管道传输到 STDIN,而必须使用 STDIN 重定向方法的情况。我怎样才能更好地在我的问题中表达这一点?
  • 重定向从来不是必要的,但您的问题暗示您认为管道更好,应尽可能避免重定向。恰恰相反:您应该尽可能使用重定向,并且只在必要时使用管道。当 shell 完全能够自行打开文件时,管道使用cat 打开文件进行读取。
  • 如果您的程序尝试seek() 或多次读取其输入文件的一部分,它不能使用管道。这意味着,例如,可以通过启动处理不同输入文件块的多个进程来并行化的sort 版本在从cat 读取时根本无法提供该功能(至少没有从首先将 FIFO 放入一个临时文件中,而不仅仅是能够让每个线程/子进程 seek() 直接访问不同的输入片段)。
  • 考虑wc -c作为另一个例子——给定一个真实文件的句柄,它可以使用stat()-family调用来获取文件在恒定时间内的长度,无论需要多长时间。给定一个管道,它必须将整个内容读取到最后并计算字节数,因此我们不仅在谈论 FIFO 开销,而且还必须使用完全不同的算法开销。

标签: linux bash redirect pipe stdin


【解决方案1】:

my_command &lt; file.txtcat file.txt | my_command 有什么区别?

my_command < file.txt 

重定向符号也可以写为0&lt;,因为这会将文件描述符0 (stdin) 重定向到file.txt,而不是当前设置,这可能是终端。如果my_command 是内置的shell,则没有创建子进程,否则有一个。

cat file.txt | my_command

这会将左侧命令的文件描述符 1 (stdout) 重定向到匿名管道的输入流,并将右侧命令的文件描述符 0 (stdin) 重定向到匿名管道的输出流管道。

我们立即看到有一个子进程,因为cat 不是内置的shell。然而在bash 中,即使my_command 是一个内置的shell,它仍然在子进程中运行。因此我们有两个子进程。

因此,理论上,管道的效率较低。这种差异是否显着取决于许多因素,包括“显着”的定义。管道 更可取的时间是这种选择:

command1 > file.txt
command2 < file.txt

这里很有可能

command1 | command2

效率更高,记住,在实践中,我们可能需要rm file.txt 中的第三个子进程。

但是,管道存在限制。它们不是可搜索(随机访问,请参阅man 2 lseek)并且它们不能内存映射(请参阅man 2 mmap)。一些应用程序将文件映射到虚拟内存,但对stdinstdout 这样做是不寻常的。尤其是在管道上(无论是匿名的还是命名的)内存映射是不可能的,因为必须保留一定范围的虚拟地址,并且需要一个大小。

编辑:

正如@JohnKugelman 所提到的,许多 SO 问题的常见错误和来源是与子进程和重定向相关的问题:

获取一个包含 99 行的文件 file.txt

i=0
cat file.txt|while read
do
   (( i = i+1 ))
done

echo "$i"

显示什么?答案是0。为什么?因为计数 i = i + 1 是在 subshel​​l 中完成的,在 bash 中,它是一个子进程并且不会更改父进程中的 i(注意:这不适用于 korn shell, ksh)。

while read
do
   (( i = i+1 ))
done < file.txt

echo "$i"

这会显示正确的计数,因为不涉及子进程。

【讨论】:

  • 这有助于提醒我 STDIN 有时可以附加到并被视为 文件。我已经习惯将其视为终端或管道的接收端,以至于我忘记了这一点。感谢您的详尽回答。
【解决方案2】:

您当然可以用从cat 读取的管道替换输入重定向的任何使用,但是这样做效率低下,因为您正在生成一个新进程来执行shell 已经可以自己执行的操作。然而,cat ... | my_command每个 实例都不能替换为 my_command &lt; ...,也就是说,当 cat 正在完成连接两个(或更多)文件的预期工作时,将其通过管道传输是完全合理的。输出到另一个命令。

cat file1.txt file2.txt | my_command

【讨论】:

  • 我不同意这对于 any 没有限定的重定向是正确的。在标准输入中使用fstatseek 的程序很少见(作为性能优化除外),但并非不可能。试图记住一个标准 GNU 工具的名称,如果给定一个管道,它会将管道的全部内容复制到一个临时文件中,以便在它能够开始工作之前有一个可搜索的源...shuf,也许?
  • @CharlesDuffy:这些正是我有兴趣听到的情况。
猜你喜欢
  • 2021-03-17
  • 1970-01-01
  • 2019-02-04
  • 2015-04-03
  • 2020-10-28
  • 2021-05-02
  • 2021-01-20
  • 2011-11-15
  • 1970-01-01
相关资源
最近更新 更多