1

我正在编写一个使用外部脚本的 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";
}

我能做些什么?

4

2 回答 2

6

Perl 常见问题解答中对此进行了介绍

假设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;

无论如何,您都应该阅读它的注意事项和警告。

于 2010-12-07T14:59:47.183 回答
1

如果大量输出被写入 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% 的时间内都有效。如果事情变得更加复杂并且进程开始阻塞或陷入僵局,这个回复只是给你一个去处。

编辑2

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

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

于 2010-12-07T20:20:48.457 回答