【问题标题】:Flush output of child process子进程的flush输出
【发布时间】:2019-02-05 15:53:51
【问题描述】:

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

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


子进程代码

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-&gt;flush(),父进程可以正确读取。
然而,这是一个示例,我必须处理不手动刷新其输出的程序。

【问题讨论】:

  • 一些快速浏览 metacpan 建议 IPC::Run 可能值得研究;它有一种在伪 tty 中运行进程的方法,可以避免您遇到的缓冲问题。

标签: perl process output buffer


【解决方案1】:

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

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

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

【讨论】:

    【解决方案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
    

    使用&gt;pty&gt;,它为@cmd中程序的STDOUT设置一个伪终端(它是一个带有&gt;的管道);另见&lt;pty&lt; 和更多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 时)。如果将&gt;pty&gt; 更改为使用&gt;(使用管道)的通常重定向,则没有终端。

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

    解决此问题的另一种方法是尽可能使用unbuffer,就像在暴民的回答中一样。

    【讨论】:

    • 如果我想在run \@cmd, '&gt;pty&gt;', sub { say "out: @_" }之前将cd到一个特定的位置,我应该如何修改这个命令?我要运行的文件不能从其他位置运行。
    • @him 你的意思是chdir?这会改变脚本的工作目录。因此,如果您在脚本中执行chdir $dir,那么如果该目录中有一个程序(例如prog),您可以在没有路径的情况下运行它(所以只需./prog 甚至可能是prog)。这直接相当于在命令行上执行cd dir,然后运行prog。是这个意思吗?
    • 我有一个脚本来运行另一个程序,在运行该命令之前,我想将我的当前目录更改为脚本内部的其他位置,仅用于运行程序 - 无法运行该程序别处。我试着写run \@cdCmd, '&gt;pty&gt;', sub { say "out: @_" }, '|', \@cmd, '&gt;pty&gt;', sub { say "out: @_" } 之类的东西,但上面写着'cd' not found ....。这可能是因为cd 是一个内置的shell 命令。所以,我想知道一种方法来做到这一点。
    • @him 好的,这就是我所说的——将chdir $dir;添加到您的脚本中,然后您可以执行run \@cmd ...$dir 是您需要所在的目录,其中程序是)。用于更改脚本工作目录的 Perl 内置函数是 chdir,您首先将其作为单独的命令运行。
    • @him 在命令行上试试这个:perl -MCwd -wE'$d=shift//".."; say "in: ", cwd; chdir $d; say "now in: ", cwd'。该程序打印工作目录,然后将其更改(默认情况下仅向上一个目录,..),然后再次打印。你可以给它的目录去:perl -MCwd -wE'...' dir-to-go-to
    猜你喜欢
    • 2018-08-10
    • 1970-01-01
    • 2013-08-17
    • 2012-11-17
    • 1970-01-01
    • 2016-11-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多