STDOUT を置き換えてると IPC::Open3 でうまくいかなかった

コマンドを実行してその結果を取得する際に,IPC::Open3::open3() を使おうと思いました。由あって STDOUT を置換していたんですが,うまくいきませんでした。

って意味ふめーなので具体的なコードで書くと,

#!/usr/bin/perl

use strict;
use warnings;
use Carp;
use Symbol;
use IPC::Open3;

warn 'normal';
run();

my $output = '';
{
    open my $handle, '>', \$output
        or confess $!;
    local *STDOUT = $handle;

    warn 'replaced';
    run();

    close $handle;
}

sub run {
    my ($ph_i, $ph_o, $ph_e) = (gensym, gensym, gensym);

    my $pid = open3($ph_i, $ph_o, $ph_e, qw'/bin/ls -l /');
    confess $! if ! defined $pid or $pid <= 0;

    close $ph_i;
    local $/;
    my $out = <$ph_o>;
    my $err = <$ph_e>;
    close $ph_o;
    close $ph_e;

    waitpid $pid, 0;
}

なコードを実行すると,

normal at test.pl line 9.
replaced at test.pl line 18.
total 45
drwxr-xr-x  2 root root  4096 Feb 10 04:05 bin
... (後略)

のようになります。つまり,*STDOUT を置換した後だと,標準出力をトラップできずに画面にでてしまう,という。

標準出力をオープンしなおしたりしようといろいろやってたんですが,あんまり芳しくなかったです。で,最終的に動いたのが,

sub run {
    my ($ph_i, $ph_o, $ph_e) = (gensym, gensym, gensym);

    use IO::Handle;
    local *STDOUT = IO::Handle->new_from_fd(1, 'w');

    my $pid = open3($ph_i, $ph_o, $ph_e, qw'/bin/ls -l /');
... (後略)

みたく,IO:Handle の fd を使って 1 番(標準出力)として設定し直す方法でした。

これで,無事,

normal at test.pl line 9.
replaced at test.pl line 18.

となり,$out 等に取得できました。