【问题标题】:How can I run a system command and die if anything is written to STDERR?如果有任何内容写入 STDERR,我如何运行系统命令并死掉?
【发布时间】:2011-05-21 15:34:00
【问题描述】:

我正在编写一个使用外部脚本的 Perl 脚本。外部脚本必须从特定目录运行,所以我发现以下内容很有用:

use IPC::System::Simple qw(capture);

my @args = ('external script path...', 'arg1', ...);
my $out = capture( [0], "cd $dir ; @args" );

有时外部脚本会向 STDERR 写入内容,但仍返回 0。我希望捕获这些时间和 confess(或 die)。由于我不控制外部脚本的返回值,我想也许我可以捕获它的 STDERR,所以我会有这样的东西:

my ($out, $err) = cool_capture( [0], "cd $dir ; @args" );
say "Output was: $out";
if ($err) {
 die "Error: this was written to STDERR: $err";
}

我能做什么?

【问题讨论】:

    标签: perl system stderr die


    【解决方案1】:

    这是 Perl 常见问题解答中的 covered

    假设test_app 是一个程序,它输出一行到stdout,一行输出到stderr:

    use IPC::Open3;
    use Symbol 'gensym';
    
    my($wtr, $rdr, $err);
    $err = gensym;
    
    my $pid = open3($wtr, $rdr, $err, 'test_app');
    
    waitpid($pid, 0);
    my $status = $? >> 8;
    
    my $stdout = <$rdr>;
    my $stderr = <$err>;
    
    print "out output: $stdout\n";
    print "err output: $stderr\n";
    
    print "Exit code: $status\n";
    

    编辑:根据请求更新以包括捕获退出代码。你也可以问perldoc IPC::Open3 哪个说

    waitpid( $pid, 0 );
    my $child_exit_status = $? >> 8;
    

    无论如何你都应该阅读它的注意事项和注意事项。

    【讨论】:

    • +1 用于在 Perl 文档中提供参考并提供答案。
    • +1 谢谢。你可以扩展它来验证test_app 的返回值是否有效(例如0,如capture( [0], ...))?
    • 如果您假设test_app 只输出一行到stdout 而一行输出到stderr,这将如何改变?如果输出更多行会发生什么变化?
    • 答案就在my $stdout = &lt;$rdr&gt;;这一行,它只捕获文件句柄的第一行。如果有更多行,您会想要捕获更多行。为简单起见,我没有打扰。
    【解决方案2】:

    如果重要的输出被写入 stdout 和/或 stderr,或者您正在读取和写入进程。您需要更加小心地处理 I/O 以避免各种阻塞问题。

    my ($wtr, $rdr, $err) ;
    
    my $pid = IPC::Open3::open3($wtr, $rdr, $err, @_);
    
    close($wtr);
    
    my $stdout = '';
    my $stderr = '';
    
    my $s = IO::Select->new;
    
    $s->add($rdr) if $rdr;
    $s->add($err) if $err;
    
    while (my @ready = $s->can_read) {
    
        foreach my $ioh (@ready) {
    
            my $bytes_read = sysread($ioh, my $chunk = '', 1024);
    
            die "read error: $!" unless $bytes_read >= 0;
    
            if ($bytes_read) {
               ($ioh eq $rdr? $stdout: $stderr) .= $chunk;
            }
        else {
                $s->remove($ioh);
            }
        }
    } 
    
    my $pid1;
    for (;;) {
    
        last if kill(0, $pid);
    
        $pid1 = wait();
        #
        # Wait until we see the process or -1 (no active processes);
        #
        last if ($pid1 == $pid || $pid1 <= 0);
    }
    

    在关闭进程之前完成阅读。如果您正在写入进程的标准输入,您还需要将 $wtr 和 syswrite 添加到上述选择循环中。

    编辑

    理由:

    对于简单的情况,上述内容可能有点过分了。当您可能移动超过几 K 的数据时,这种输入和输出的高级处理就会发挥作用。

    例如,如果您正在执行“df”命令,则不需要它。

    但是,当 stdin、stdout 或 stderr 中的任何一个的系统缓冲区填满时,阻塞变得可能,事情可能会变得更加复杂。

    如果子进程填满了 stderr 和/或 stdout 缓冲区,它可能会阻塞并等待您清除它们。但是,如果您在从 stdout 或 stderr 读取之前等待进程完成;那是一个僵局。您可能会看到系统调用永远不会完成,子进程也永远不会完成。

    如果正在写入标准输入,也有类似的死锁可能性,但子进程无法使用输入。在子进程消耗输入并写入标准输出的“管道”情况下,这种情况尤其可能发生。

    选择循环是关于逐步清除缓冲区以避免阻塞。 stdout 和 stderr 被同时监控。

    如果您正在写入标准输入并从标准输出(管道)读取,您需要保持标准输出和标准错误清晰,并且仅在标准输入准备好接收输入时写入。

    只需等待进程完成,然后读取 stdout/stderr 可能在 90% 的时间内都有效。如果事情变得更加复杂并且进程开始阻塞或陷入僵局,此回复只是为了给您一个去处。

    EDIT2

    至于使用哪个,我想说从简单开始,努力测试。

    采用 Sorpigal 的方法,但尝试在更高的数据量和更困难的负载和条件下进行压力测试,这是您在实时系统中所期望的。

    【讨论】:

    • 你能解释一下为什么需要所有这些吗?
    • @David B:我已经添加了一些解释。希望这会有所帮助。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-03-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-10-10
    • 2011-10-07
    • 1970-01-01
    相关资源
    最近更新 更多