刷新子进程的输出

问题描述 投票:2回答:2

我通过IPC::Open2创建了一个子进程。 我需要逐行读取这个子进程的标准输出。 问题是,由于子进程的stdout没有连接到终端,所以它是完全缓冲的,在进程终止之前我无法读取它。

如何在不修改代码的情况下刷新子进程的输出?


子进程代码

while (<STDIN>) {
    print "Received : $_";
}

父进程代码:

use IPC::Open2;
use Symbol;

my $in = gensym();
my $out = gensym();

my $pid = open2($out, $in, './child_process');

while (<STDIN>) {
    print $in $_;
    my $line = <$out>;
    print "child said : $line";
}

当我运行代码时,它会等待子进程的输出。 但是,如果我用bc运行它的结果是我所期望的,我相信bc必须手动清除其输出

注意: 在子进程中,如果我在开头添加$| = 1或在打印后添加STDOUT->flush(),则父进程可以正确地从中读取。 然而,这是一个示例,我必须处理不手动刷新其输出的程序。

perl process output
2个回答
2
投票

一种方法是为进程建立类似终端的环境,即伪终端(pty)。这很难做到并且非常依赖系统,但是IPC::Run已经准备好了这个功能。

这是驱动程序,使用at工具运行,因此它没有控制终端(或通过cron运行)

use warnings;
use strict;
use feature 'say';

use IPC::Run qw(run);

my @cmd = qw(./t_term.pl input arguments); 

run \@cmd, '>pty>', sub { say "out: @_" };

#run \@cmd, '>', sub { say "out: @_" }   # no pty

使用>pty>,它为STDOUT中的@cmd设置了一个伪终端(它是>的管道);另见<pty<,看到更多about redirection。每次有来自孩子的输出时,都会调用匿名的sub {},因此可以随时处理它。还有其他选择。

被称为(t_term.pl)的程序仅测试终端

use warnings;
use strict;
use feature 'say';

say "Is STDOUT filehandle attached to a terminal: ",
    ( (-t STDOUT) ? "yes" : "no" );
sleep 2;
say "bye from $$";

在这个例子中,-t STDOUT(参见filetest operators)是一种检查终端的合适方法。有关更多/其他方式,请参阅this post

输出显示被调用程序(t_term.pl)确实在其STDOUT上看到一个终端,即使驱动程序没有运行(使用at,或者当用完crontab时)。如果使用>pty>(使用管道)将>更改为通常的重定向,则没有终端。

这是否解决了缓冲问题显然取决于该程序,以及它是否足以用终端来欺骗它。

绕过这个问题的另一种方法是尽可能使用unbuffer,就像在mob的回答中一样。


3
投票

不幸的是,Perl无法控制它执行的程序的缓冲行为。有些系统有unbuffer实用程序,可以做到这一点。如果您可以访问此工具,您可以说

my $pid = open2($out, $in, 'unbuffer ./child_process');

有一个关于Windows的等效工具的讨论here,但我不能说它们是否有效。

© www.soinside.com 2019 - 2024. All rights reserved.