【问题标题】:Why does taking stdin from a file differ from receiving it over a pipe?为什么从文件中获取标准输入与通过管道接收标准输入不同?
【发布时间】:2012-01-27 12:45:21
【问题描述】:

使用 bash,我经常想要获取大型 csv 文件的标题并在其余部分中搜索特定条目。我这样做如下。

$ (head -1; grep mike) < tmp.csv
name,age,favourite colour
mike,38,blue

但是从 cat 或任何其他命令获取输入都不起作用 - 似乎 grep 永远不会传递文件的其余部分。

$ cat tmp.csv | (head -1; grep mike)
name,age,favourite colour

为什么在这两种情况下会有不同的行为?

【问题讨论】:

  • wc -l test.txt -output-> 3 (head -1&gt;/dev/null; wc -l) &lt; test.txt -outupt-> 2. !
  • 我有一些理论,但我无法用 bash 3.2.48 (Darwin) 重现这一点。你用的是哪个版本?
  • 这是 GNU bash,版本 3.2.25(1)-release (x86_64-redhat-linux-gnu),在 rhel 5.6 机器上。

标签: bash unix pipe


【解决方案1】:

从管道读取和从文件读取的区别在于您可以lseek 在文件上,但不能在管道上。

这里的行为看起来(通过strace 看到)就像来自head,而不是bash。 head 将读取一个缓冲区并找到适当数量的行,然后lseek 回到最后一个输出行结束的点,使文件句柄在该位置保持打开状态。如上所述,如果它正在读取文件,则此方法有效,但如果它从管道中读取,则无效。

除了head 中的这种行为有意义的情况外,我想不出任何其他情况其他,但确实如此。我告诉你,每天都能学到新东西......

【讨论】:

    【解决方案2】:

    很奇怪。您不应该依赖这种未记录的行为,而是使用类似的东西:

    sed -n '1p;/mike/p' tmp.csv
    

    【讨论】:

    • 这不是无证行为。一切都按预期工作(请参阅邪恶的 ottos 答案)。这只是一个不好的做法 - 使用 (command1; command2) 构造从公共标准输入中读取数据,因为第二个依赖于第一个的行为。所以你的命令也是对的。
    【解决方案3】:

    我无法使用 bash 3.2.48 可靠地重现这一点。要么都成功,要么都失败。但失败的根本原因是文件有多大。

    cat 读取一个缓冲区(4k-64k 取决于系统)并将其传递到管道中。 head 消耗整个缓冲区然后退出。 grep 然后可以在缓冲区大小之后访问文件。在我的系统上,我只能将您的管道用于grep,而不是文件中的一个缓冲区(因此我可以在一个长文件的末尾使用grep,但在使用head 之后不能在开头使用)。

    更高版本的 bash 可能会优化 &lt; 运算符(但不是 cat)以允许您的技巧发挥作用,但我不认为这是受支持的行为。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2015-01-18
      • 1970-01-01
      • 1970-01-01
      • 2014-01-04
      • 1970-01-01
      • 2020-02-12
      • 1970-01-01
      相关资源
      最近更新 更多