外部プログラム実行時にエラー出力と終了ステータスも取得する
外部プログラムを実行した時に出力される値には
- 標準出力
- 標準エラー出力
- 終了ステータス
があります。
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を使用する
調べてみるとCPANにIPC::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
これで、デッドロックされずにちゃんと取得できるようになります。
参考にさせてもらったサイト
- http://d.hatena.ne.jp/kazuhooku/20100813/1281690025
- http://f99aq.hateblo.jp/entry/20110502/1304264528
- http://www.manami.st/clog/2007-07-11-1.html
Perlクックブック(Volume2)のレシピ16-9にこの辺の話が詳しく載っています。