我不明白数据如何在管道中流动,希望有人能澄清那里发生了什么。
我认为命令管道以逐行的方式处理文件(文本、字符串数组)。 (如果每个命令本身逐行工作。)每一行文本都通过管道,命令不会等待前一行完成整个输入的处理。
但似乎并非如此。
这是一个测试示例。有一些文字行。我将它们大写并重复每行两次。我这样做与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 时(以先到者为准),它会写入第二个管道。cat
Entertr
stdio
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
。我建议答案可能埋在源代码中tr
,sed
而不是管道问题。
编辑:
我看见乌普斯了提供了解释当我正在输入最后一次编辑时。谢谢!