管道有时不会导致立即输出

时间:2015-11-04 18:40:54

标签: bash pipe pipeline buffering

我现在观察了几次A | B | C可能不会导致立即输出,尽管A不断产生输出。我不知道这怎么可能。根据我的理解,所有三个进程应该同时工作,将它们的输出放入下一个管道(或stdout),并在它们完成一步时从前一个管道中取出。

以下是我目前遇到的一个例子:

tcpflow -ec -i any port 8340 | tee second.flow | grep -i "\(</Manufacturer>\)\|\(</SerialNumber>\)" | awk -F'[<>]' '{print $3}'

应该发生什么:

我看一下tcp包的端口。如果出现问题,它应该是某种XML格式,我想从这些软件包中获取制造商和序列号。我还希望在文本文件&#34; second.flow&#34;中获得完整的,未修改的输出,以供日后参考。

会发生什么:

一切都符合要求,但不是每10秒钟输出一次(我确定每十秒钟就会得到这些输出!)我必须等待很长时间,然后立即打印很多。它就像其中一个工具吞噬了缓冲区中的所有内容,只有在缓冲区已满时才会打印出来。我不想要那个。我希望尽可能快地获得每一行。

如果我将tcpflow ...替换为cat second.flow,则会立即生效。有人可以描述一下发生了什么吗?如果显而易见,还有另一种方法可以达到相同的效果吗?

2 个答案:

答案 0 :(得分:3)

一系列管道中的每一层都可能涉及缓冲;默认情况下,未指定stdout缓冲行为的工具将在输出到终端时使用行缓冲,并在输出到其他任何位置时阻止缓冲(包括管道到另一个程序或文件)。在链式管道中,除了最后一个阶段之外的所有阶段都会看到它们的输出不会到达终端,并且会阻塞缓冲区。

因此,在您的情况下,tcpflow可能会不断产生输出,如果它正在这样做,tee应该几乎以相同的速率生成数据。但grep将限制流向涓流,并且在涓流超过输出缓冲区大小之前不会产生输出。它已经执行了过滤并调用了fwriteputsprintf,但数据在将其发送到awk之前等待足够的字节在其后面构建减少(昂贵的)系统调用的数量。

cat second.flow立即生成输出,因为只要cat完成输出,它就会退出,刷新并关闭进程中的stdout,当每个步骤找到它时stdin stdout tcpflow grep 1}}在EOF,它退出,刷新并关闭它的tcpflowtee没有退出,所以EOF和级联的级联没有发生。

对于某些程序,在一般情况下,您可以使用stdbuf(或unbuffer更改缓冲行为,但这不能进行行缓冲来平衡效率,并且存在管道问题输入)。如果程序使用内部缓冲,这仍然可能不起作用,但值得一试。

在你的特定情况下,因为它可能stdout导致中断(通过仅产生一个粘在缓冲区中的涓流输出,其中tcpflow -ec -i any port 8340 | tee second.flow | grep -i --line-buffered "\(</Manufacturer>\)\|\(</SerialNumber>\)" | awk -F'[<>]' '{print $3}' grep是生成一个torrent,并且awk连接到tcpflow,因此默认情况下缓冲行),你只需将命令行调整为:

stdbuf

至少对于Linux's grep(不确定switch是否是标准的),这使tee明确地将其自己的输出缓冲更改为面向行的缓冲,这应该消除延迟。如果stdbuf本身没有产生足够的输出来定期刷新(你暗示它确实存在,但你可能错了),你可以使用stdbuf(但不是stdbuf -oL tcpflow -ec -i any port 8340 | tee second.flow | grep -i --line-buffered "\(</Manufacturer>\)\|\(</SerialNumber>\)" | awk -F'[<>]' '{print $3}' ,每个awk手册页注释,手动更改其缓冲,因此stdout不做任何事情)使它们行缓冲:

system("")

从评论更新:看起来有些print块缓冲区打印到fflush(),即使连接到终端也是如此。 For mawk (the default on many Debian based distros), you can non-portably disable it by passing the -Winteractive switch at invocation。或者,为了便于上班,您可以在awk awk之后拨打fflush()。遗憾的是,显而易见的<target name="compile" depends="init,copy-non-java-files"> <javac srcdir="${src}" destdir="${build}" source="1.6"> <classpath> <pathelement path="${java.class.path}/" /> <fileset dir="*\\eclipse\\plugins"> <include name="**/*.jar" /> </fileset> <fileset dir="${lib}"> <include name="**/*.jar" /> </fileset> </classpath> </javac> </target> 无法移植到较早的indexes实现中,但如果您只关心现代df,则只需使用axis=1即可,并且大部分都是可移植的。< / p>

答案 1 :(得分:1)

减少缓冲

管道中的每个应用程序都可以自己进行缓冲。您可能想看看是否可以减少tcpflow中的缓冲,因为其他命令是面向行的,并且不太可能成为缓冲问题的根源。我没有在tcpflow中看到缓冲区控制的任何特定选项,尽管 max_bytes -b标志可能有助于您要使用的文本靠近流的前端的情况

您还可以尝试使用stdbuf from GNU coreutils修改tcpflow的缓冲。这可能有助于减少管道中的延迟,但手册页提供了以下警告:

  

注意:如果COMMAND调整其标准流的缓冲(例如'tee'),则会通过'stdbuf'覆盖相应的更改。另外一些过滤器(如'dd'和'cat'等)不使用I / O流,因此不受'stdbuf'设置的影响。

例如,以下可能减少tcpflow的输出缓冲:

  • stdbuf --output=0 tcpflow -ec -i any port 8340 # unbuffered output
  • stdbuf --output=L tcpflow -ec -i any port 8340 # line-buffered output

除非上述警告之一适用。您的里程可能会有所不同。