【问题标题】:Avoid buffering when parsing stdout with Perl使用 Perl 解析标准输出时避免缓冲
【发布时间】:2014-11-17 20:37:21
【问题描述】:

我想使用 Perl 逐行解析外部程序(一些 shell 命令)的输出。该命令连续运行,因此我将其放入一个线程并使用共享变量与我的主程序进行通信。

到目前为止,我的代码看起来类似于

#!/usr/bin/perl

use warnings;
use strict;
use threads;
use threads::shared;

my $var :shared; $var="";

threads->create(
    sub {
        # command writes to stdout each ~100ms
        my $cmd = "<long running command> |";
        open(README, $cmd) or die "Can't run program: $!\n";
        while(<README>) {
            my $line = $_;
            # extract some information from line
            $var = <some value>;
            print "Debug\n";
        }
        close(README);
    }
);

while(1) {
    # evaluate variable each ~second
    print "$var\n";
    sleep 1;
}

对于某些命令,这工作得非常好,并且行在它们进入时就被处理。输出类似于:

...
Debug
Debug
...
<value 1>
...
Debug
Debug
...
<value 2>
...

但是,对于其他命令,这表现得很奇怪,并且正在逐块处理这些行。所以$var 没有得到更新,Debug 也有一段时间没有打印出来。然后,突然输出是(类似于):

...
<value 1>
<value 1>
<value 1>
...
Debug
Debug
Debug
...
<value 20>

并且$var 设置为最后/当前值。然后重复。解析总是延迟并分块完成,而 $var 之间不会更新。

首先:除了使用管道之外,还有什么更好/更合适的方法来解析外部程序的输出(逐行!)?

如果没有,我该如何避免这种行为?

我读过,使用autoflush(1);$|=1; 可能是一种解决方案,但仅适用于“当前选择的输出通道”。我将如何在我的上下文中使用它?

提前谢谢你。

【问题讨论】:

  • “长时间运行的命令”的输出没有被刷新。有一个名为unbuffer 的实用程序可以欺骗在连接到终端时使用行缓冲约定的程序。
  • 有没有办法控制命令的刷新行为?但是该命令每 100 毫秒打印一次到“stdout”。 shell stdout 和管道到 perl 的区别在哪里?
  • 我会考虑从命令行运行长时间运行的命令并通过管道传输到perl -n script
  • @ikegami 那么将$cmd = "&lt;long running command&gt; |"; 更改为$cmd = "unbuffer &lt;long running command&gt; |"; 应该可以吗?
  • 是的。您也可以自己创建伪 tty。 IPC::Run 可以轻松做到这一点

标签: perl parsing buffer stdout autoflush


【解决方案1】:

在一般情况下,您的脚本无法更改子进程输出的缓冲。在某些特定情况下,您可以通过使用适当的开关来启动它,但仅此而已。

我建议不要编写自己的代码来执行运行和读取,而是重新编写脚本以使用IPC::Run 模块。它的存在就是为了解决这类问题。文档不是有史以来最好的,但模块本身经过充分测试且可靠。

【讨论】:

  • 如何在我的主程序中解析输出时将命令发送到后台?根据我尝试start()pump()的文档,我的命令是:my $h = start \@cmd, \$in, \$out; pump $h; print $out;。但是,我得到的不是一行,而是一整行。如果我重复泵送(没有完成)命令将重新执行而不是继续。我错过了什么吗?
  • 正如我之前提到的,IPC::Run 能力的伪 tty 创建有帮助。你用过吗?
【解决方案2】:

感谢 ikegamiCalle Dybedahl,我为我的问题找到了以下解决方案:

#!/usr/bin/perl

use warnings;
use strict;
use threads;
use threads::shared;
use sigtrap qw(handler exit_safely normal-signals stack-trace error-signals);
use IPC::Run qw(finish pump start);

# define shared variable
my $var :shared; $var="";

# define long running command
my @cmd = ('<long running command>','with','arguments');
my $in = '';
my $out = '';
# start harness
my $h = start \@cmd, '<pty<', \$in, '>pty>', \$out;

# create thread
my $thr = threads->create(
    sub {
        while (1) {
            # pump harness
            $h->pump;
            # extract some information from $out
            $var = <some value>;
            # empty output
            $out = '';
        }
    }
);

while(1) {
    # evaluate variable each ~second
    print "$var\n";
    sleep 1;
}

sub exit_safely {
    my ($sig) = @_;
    print "Caught SIG $sig\n";
    # harness has to be killed, otherwise
    # it will continue to run in background
    $h->kill_kill;
    $thr->join();
    exit(0);
}

exit(0);

【讨论】:

  • 我怀疑是否有必要使用'&lt;pty&lt;', \$in 而不是'&lt;', \$in
  • 为什么不使用子引用而不是标量引用作为输出。您不再需要抽水了。
猜你喜欢
  • 1970-01-01
  • 2012-09-20
  • 1970-01-01
  • 1970-01-01
  • 2013-11-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-10-27
相关资源
最近更新 更多