CAF*_*FxX 12 linux pipe buffer fifo
假设您有一个如下所示的管道:
$ a | b
如果b
停止处理标准输入,一段时间后管道填满,并从a
标准输出写入,将阻塞(直到b
再次开始处理或死亡)。
如果我想避免这种情况,我可能会尝试使用更大的管道(或更简单地说,buffer(1)
),如下所示:
$ a | buffer | b
这只会为我争取更多时间,但最终a
会停止。
我想要的(对于我正在解决的一个非常具体的场景)是有一个“泄漏”的管道,当它满了时,会从缓冲区中删除一些数据(理想情况下,逐行)让我们a
继续处理(正如您可能想象的那样,在管道中流动的数据是可消耗的,即处理数据b
不如a
能够无阻塞地运行重要)。
总而言之,我希望有一个像有界的、泄漏的缓冲区之类的东西:
$ a | leakybuffer | b
我可能可以用任何语言很容易地实现它,我只是想知道是否有我遗漏的“准备使用”(或类似 bash one-liner 之类的东西)。
注意:在示例中我使用的是常规管道,但该问题同样适用于命名管道
虽然我给出了下面的答案,但我也决定实施leakybuffer 命令,因为下面的简单解决方案有一些限制:https : //github.com/CAFxX/leakybuffer
Mat*_*lis 14
最简单的方法是通过一些设置非阻塞输出的程序。这是一个简单的 perl oneliner(您可以将其另存为leakybuffer),它是这样做的:
所以你a | b
变成:
a | perl -MFcntl -e \
'fcntl STDOUT,F_SETFL,O_NONBLOCK; while (<STDIN>) { print }' | b
Run Code Online (Sandbox Code Playgroud)
什么是读取输入并写入输出(与 相同cat(1)
)但输出是非阻塞的 - 这意味着如果写入失败,它将返回错误并丢失数据,但该过程将继续下一行输入,因为我们可以方便地忽略错误。进程是一种行缓冲,如您所愿,但请参阅下面的警告。
你可以测试,例如:
seq 1 500000 | perl -w -MFcntl -e \
'fcntl STDOUT,F_SETFL,O_NONBLOCK; while (<STDIN>) { print }' | \
while read a; do echo $a; done > output
Run Code Online (Sandbox Code Playgroud)
你会得到output
丢失行的文件(确切的输出取决于你的shell的速度等),如下所示:
12768
12769
12770
12771
12772
12773
127775610
75611
75612
75613
Run Code Online (Sandbox Code Playgroud)
您会看到 shell 在 之后丢失行的位置12773
,但也是一个异常 - perl 没有足够的缓冲区用于12774\n
但做了 for1277
所以它只写了那个 - 所以下一个数字75610
不会从行的开头开始,使它很小丑陋的。
可以通过让 perl 检测何时写入未完全成功来改进这一点,然后在忽略新行的同时尝试刷新该行的剩余部分,但这会使 perl 脚本更加复杂,因此留作练习感兴趣的读者:)
更新(对于二进制文件): 如果您不处理换行符终止的行(如日志文件或类似文件),您需要稍微更改命令,否则 perl 将消耗大量内存(取决于换行符在您的输入中出现的频率):
perl -w -MFcntl -e 'fcntl STDOUT,F_SETFL,O_NONBLOCK; while (read STDIN, $_, 4096) { print }'
Run Code Online (Sandbox Code Playgroud)
它也适用于二进制文件(不消耗额外的内存)。
Update2 - 更好的文本文件输出:
避免输出缓冲区(syswrite
而不是print
):
seq 1 500000 | perl -w -MFcntl -e \
'fcntl STDOUT,F_SETFL,O_NONBLOCK; while (<STDIN>) { syswrite STDOUT,$_ }' | \
while read a; do echo $a; done > output
Run Code Online (Sandbox Code Playgroud)
似乎为我解决了“合并线”的问题:
12766
12767
12768
16384
16385
16386
Run Code Online (Sandbox Code Playgroud)
(注意:可以使用perl -ne '$c++; next if $c==$_; print "$c $_"; $c=$_' output
oneliner验证在哪一行输出被切断)