管道,数据如何在管道中流动?


22

我不了解数据在管道中的流动方式,希望有人可以澄清其中的情况。

我认为命令管道以逐行方式处理文件(文本,字符串数组)。(如果每个命令本身一行一行地工作。)文本的每一行都通过管道,命令不必等待前一个命令完成对整个输入的处理。

但似乎并非如此。

这是一个测试示例。有几行文字。我将它们大写,并重复每行两次。我这样做cat text | tr '[:lower:]' '[:upper:]' | sed 'p'

要遵循此过程,我们可以“交互式”运行它-跳过中的输入文件名cat。管道的每一部分都逐行运行:

$ cat | tr '[:lower:]' '[:upper:]'
alkjsd
ALKJSD
sdkj
SDKJ
$ cat | sed 'p'
line1
line1
line1
line 2
line 2
line 2

但是完整的管道确实在等我完成输入,EOF然后才打印结果:

$ cat | tr '[:lower:]' '[:upper:]' | sed 'p'
I am writing...
keep writing...
now ctrl-D
I AM WRITING...
I AM WRITING...
KEEP WRITING...
KEEP WRITING...
NOW CTRL-D
NOW CTRL-D

应该是这样吗?为什么不逐行显示?


这不是管道,而是cat缓冲直到stdin关闭。
goldilocks

但是,tr并且sedcatstdin关闭之前开始执行生产线
xealits 2015年

stdio(我相信所有提到的程序都使用)的默认值是stderr是未缓冲的,并且stdout在写入终端时是行缓冲的,否则是完全缓冲的(例如,如果它正在写入文件或管道) 。一些命令具有可以更改标准输出缓冲的标志,但看起来tr却没有。
kasperd'2

Answers:


36

stdio大多数Unix程序都使用通用的缓冲规则,然后是C标准I / O库()。如果输出将输出到终端,则在每行的末尾将其刷新;否则,仅在缓冲区(我的Linux / amd64系统上为8K;您的设备可能不同)上才刷新。

如果你所有的事业都按照一般规则,你会看到在所有的例子输出延迟(cat|sedcat|tr,和cat|tr|sed)。但是有一个例外:GNU cat从不缓冲其输出。它要么不使用,要么stdio更改默认的stdio缓冲策略。

我可以肯定地确定您使用的是GNU cat而不是其他的Unix,cat因为其他人则不会这样。传统的UNIX cat可以-u选择请求无缓冲的输出。GNU cat忽略该-u选项,因为它的输出始终是无缓冲的。

因此cat,在GNU系统中,只要您在左侧有一个带有a 的管道,就不会延迟数据通过管道的传递。该cat甚至不打算一行行-你的终端做。在输入cat的输入时,终端处于基于行的“规范”模式,并通过诸如backspace和ctrl-U之类的编辑键为您提供了在发送之前编辑输入的行的机会Enter

在该cat|tr|sed示例中,只要按,tr仍会从中接收数据,但遵循默认策略:其输出将进入管道,因此不会在每行之后刷新。当缓冲区已满或接收到EOF时(以先到者为准),它将写入第二个管道。catEntertrstdio

sed也遵循stdio默认策略,但是其输出将发送到终端,因此它将在完成每行后立即写入每行。这会影响在管道的另一端显示内容之前必须键入的数量-如果sed正在缓冲其输出,则必须键入两倍的数量(以填充tr's输出缓冲区 sed 's输出缓冲)。

GNU sed具有-u选项,因此,如果您撤销订单并使用它,cat|sed -u|tr您将看到输出立即再次出现。(该sed -u选项可能在其他地方可用,但我不认为它是古老的Unix传统cat -u)。据我所知,没有等效的选项tr

有一个名为的实用程序stdbuf,可让您更改任何使用stdio默认值的命令的缓冲模式。这有点脆弱,因为它LD_PRELOAD用来完成C库本来不打算支持的事情,但是在这种情况下,它似乎可以工作:

cat | stdbuf -o 0 tr '[:lower:]' '[:upper:]' | sed 'p'

1
谢谢!很棒的答案。也许我应该以某种方式提及问题中的缓冲,以便人们可以找到它。
xealits 2015年

tee而且dd通常也会按照自己的规则进行比赛。如果将它们进行富有想象力的组合,则这三个工具几乎可以完全消除对stdbuf后台管道的任何需求。
mikeserv

1
这是避免不必要地使用猫的原因之一。
hobbs 2015年

8

这实际上使我花了一些时间去理解甚至是回答。很好的问题(我接下来将投票表决)。

您忽略了尝试tr | sed上面的调试项:

>tr '[:lower:]' '[:upper:]' | sed 'p'
i am writing
still writing
now ctrl-d
I AM WRITING
I AM WRITING
STILL WRITING
STILL WRITING
NOW CTRL-D
NOW CTRL-D
>

因此显然是tr缓冲。每天学些新东西!

编辑

考虑到这一点,我们已经隔离了原因,但没有提供解释。如果cat | tr,它写入向右走,如果你cat | sed,它写入向右走,但如果你tr | sed,它等待EOF。我建议答案可能被埋藏在其中trsed源代码中,而不是管道问题。

编辑

我输入最后一次编辑时看到Wumpus 提供了解释。谢谢!


1
确实他们缓冲!如Wumpus所述,使用大约8kb的行进行的测试表明缓冲区确实为8Kb。我想接受两个答案以分享一些声望,但我将Wumpus的答案作为更完整的答案。不管怎么说,还是要谢谢你!
xealits 2015年

1
没问题,我的是经验性答案,他的是知识渊博的答案。
Poisson Aerohead,2015年

另请参阅此问题,该问题显示了如何使用stdbuf,这可能也会有所帮助。unix.stackexchange.com/questions/182537/…–
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.