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

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

我不明白数据如何在管道中流动,希望有人能澄清那里发生了什么。

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

但似乎并非如此。

这是一个测试示例。有一些文字行。我将它们大写并重复每行两次。我这样做与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

应该是这样吗?为什么不是一行一行的呢?

答案1

stdio大多数 UNIX 程序使用的C 标准 I/O 库 ( ) 遵循一个通用缓冲规则。如果输出要发送到终端,则它会在每行末尾刷新;否则,仅当缓冲区(我的 Linux/amd64 系统上为 8K;您的系统上可能有所不同)已满时才会刷新。

如果您的所有实用程序都遵循一般规则,您将在所有示例( 、 和 )中看到cat|sed输出cat|tr延迟cat|tr|sed。但有一个例外:GNUcat从不缓冲其输出。它要么不使用stdio,要么更改默认stdio缓冲策略。

我可以相当肯定你使用的是 GNUcat而不是其他 unix,cat因为其他 unix 不会这样做。传统 unixcat有一个-u请求无缓冲输出的选项。GNUcat忽略了该-u选项,因为它的输出始终是无缓冲的。

因此,只要有一个cat左边带有 的管道,在 GNU 系统中,数据通过管道的传递就不会被延迟。甚至不是cat逐行进行 - 你的终端正在这样做。当您为 cat 键入输入时,您的终端处于“规范”模式 - 基于行,使用退格键和 ctrl-U 等编辑键,让您有机会在使用 发送之前编辑您键入的行Enter

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

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

GNUsed-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'

答案2

这实际上需要我一些思考才能理解,甚至需要更多的时间才能回答。很好的问题(接下来我会投票)。

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而不是管道问题。

编辑:

我看见乌普斯了提供了解释当我正在输入最后一次编辑时。谢谢!

相关内容