totofugaのブログ

ネットワークとかc言語、perlの話。

外部プログラム実行時にエラー出力と終了ステータスも取得する

外部プログラムを実行した時に出力される値には

があります。

http://codepad.org/duNbERYM

use strict;
use warnings;
 
print "std out print";
warn "std err print\n";
 
exit(99);

のようなテストプログラムのすべての値を取得したいとします。

標準出力の取得

perlには外部プログラムを実行する便利な構文としてバッククォートがあるので

my $std_output = `output.pl`;

のように使用すれば標準出力はすぐに取得することができます。

標準エラー出力の取得

先ほどのプログラムでは標準エラー出力が親プロセスと同じになってしまうため 画面にそのまま表示されてしまいます。

そこで標準エラー出力をキャプチャしてみました。

#!/usr/bin/env perl
use strict;
use warnings;
use feature qw(say);

my $std_err;
my $std_out;
{
    open my $err_handle, '>', \$std_err or die "open error";
    local *STDERR = *$err_handle;

    $std_out = `./print.pl`;
}

say "std-out: $std_out";
say "std-err: $std_err";

しかし結果は、

std err print
std-out: std out print
Use of uninitialized value $std_err in concatenation (.) or string at /root/abc/test.pl line 16.
std-err: 

どうやらSTDERRは子に反映されないようです。

open3を使用する

調べてみるとCPANIPC::Open3と言うものがあり、 標準出力と標準エラー出力を分けてとれるようになるみたいです。

ということで簡単に書いてみたのですが、

#!/usr/bin/env perl
use strict;
use warnings;
use IPC::Open3;
use File::Spec;
use feature qw(say);
use Symbol;

my ($stdout_handle, $stderr_handle) = (gensym, gensym);
my $pid = open3(undef, $stdout_handle, $stderr_handle, './print.pl') or die "error $?";

waitpid($pid, 1);

my $std_out = <$stdout_handle>;
my $std_err = <$stderr_handle>;

say "std-out: $std_out";
say "std-err: $std_err";

しかし上記のコードだと http://d.hatena.ne.jp/kazuhooku/20100813/1281690025で kazuhookuさんが言っているように、デッドロックを起こしてしまいます。

修正してみると

#!/usr/bin/env perl
use strict;
use warnings;
use IPC::Open3;
use File::Spec;
use feature qw(say);
use Symbol;
use IO::Select;

my ($stdout_handle, $stderr_handle) = (gensym, gensym);
my $pid = open3(undef, $stdout_handle, $stderr_handle, './print.pl') or die "error $?";

my $print_selector = IO::Select->new($stdout_handle, $stderr_handle);

my $std_out;
my $std_err;
while ( my @redy = $print_selector->can_read ) {
    foreach my $handle ( @redy ) {
        if ($handle == $stdout_handle ) {
            $std_out = <$stdout_handle>;
        } else {
            $std_err = <$stderr_handle>;
        }

        $print_selector->remove($handle) if eof($handle);
    }
}

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

say "std-out:   $std_out";
say "std-err:   $std_err";
say "exit_code: $exit_code";

結果

std-out:   std out print
std-err:   std err print
exit_code: 99

これで、デッドロックされずにちゃんと取得できるようになります。

参考にさせてもらったサイト

Perlクックブック(Volume2)のレシピ16-9にこの辺の話が詳しく載っています。