奇怪的管道缓冲

4
我有一个文件,其中包含一系列文件编号(从0开始)。
$ cat in.del
0
1
2
....

请问有人能够解释一下这里发生了什么以及除了管道以外还在哪里进行缓冲? 据我所知,head的标准输入文件描述符(fileno)必须直接指向管道的读取端。

$ cat in.del | ( head -n1 ; head -n1 )
0
60

以下代码与上面的代码有何不同?
$ cat in.del | ( head -n10 ; head -n10 )
0
1
...
8
9
60
1861 # O_o
1862
1863
...
1868
1869

这个工作按预期运行,证明head本身不会读取比其向stdout写入的更多字节:

$ ( head -n10 ; head -n10 ) < ./in.del
0
1
...
9
10
11
...
18
19

显然与管道有关联发生
更新
操作系统:Ubuntu 18.04.1 LTS
Bash: 版本 4.4.19(1)-release (x86_64-pc-linux-gnu)
第二次更新
作为 @Barmar 给出的绝妙答案的补充信息,更多 stdio 缓冲区内容请查看 此链接

在我的 Mac 上,最后一个示例与第二个示例的效果相同。但是在 Debian Linux 上,我得到了与您相同的结果。 - Barmar
@Barmar,已更新问题并提供了系统信息。此外,我确认在Mac上出现了您描述的相同行为(最后一个示例与第二个示例输出相同),尽管输出的行号不同。 - DimG
我偶然发现了这个问题,因为我遇到了类似的问题。我需要使用variable=$(head -n 1)来支持我的脚本。我找到的解决方法是改用read -r variable。这似乎也适用于上面的例子cat in.del | ( read -r line; echo $line ; read -r line ; echo $line ),以防其他人(或将来的我)仍需要在他们的脚本中使用管道支持。 - bigbear3001
1个回答

5
现在发生的情况是,stdio从管道中一次性读取整个缓冲区,而在Linux上缓冲区大小为8K。然后,head从缓冲区中读取前10行,打印它们并退出。下一个head从上一个结束的地方,在文件的8K字节处开始从管道中读取。它读取该行和接下来的9行。你看到的60是1860的结尾。之所以在最后一种情况下按预期工作是因为head在退出之前会寻找打印的最后一行的末尾。寻找在管道中不起作用,因此没有效果。但是当stdin是普通文件时,寻找有效,并且下一个进程从寻找设置文件位置的地方开始。我在我的Mac上看到略有不同的结果。它的缓冲区大小为64K,因此第二个head在文件中开始得更晚。它还在退出之前不会回到最后一个打印的行的末尾,因此文件重定向版本与管道相同。

有道理,谢谢! 你在我的实验设置方面做得很好:一旦我发现这是由于缓冲区化,我应该将行号更改为 ^<line_number>$ 等内容,然后就会发现 60 不是行号 60 - DimG
是的,第一次测试确实很混乱。在第二次测试中,您可以看到下一行是“1861”,而其前面的一行应该是“1860”。 - Barmar
1
顺便说一下,应该用“缓冲”,而不是“缓冲化”。 - Barmar

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接