http://qs1969.pair.com?node_id=652944

rvosa has asked for the wisdom of the Perl Monks concerning the following question:

Dear monks,

I am trying to capture the STDERR from a process I'm launching with system(). I thought I had it all figured out by doing:
use IO::Handle; my @logmessages; my $stderr = IO::Handle->new; $stderr->autoflush( 1 ); $stderr->fdopen( fileno( STDERR ), 'r' ); system( 'command', 'with', 'args' ); while( defined( my $line = $stderr->getline ) ) { push @logmessages, $line; } $stderr->close; # now do something useful with @logmessages
However, things seem to get stuck in the while loop. What am I doing wrong? What's the canonical way to capture STDERR (I know, I know, TMTOWTDI so I may not need the canonical way, just one that works).

Thanks!

Replies are listed 'Best First'.
Re: Capturing STDERR using IO::Handle
by ikegami (Patriarch) on Nov 26, 2007 at 09:28 UTC

    IPC::Open3's open3 is one way.

    use IPC::Open3 qw( open3 ); use Symbol qw( gensym ); my $pid = open3( my $to_chld = gensym(), my $fr_chld = gensym(), my $fr_chld_err = gensym(), 'command', 'with', 'args' ); close($to_chld); close($fr_chld); my @logmessages = <$fr_chld_err>; waitpid($pid, 0);

    Untested.

      IO::CaptureOutput is another way.

      use IO::CaptureOutput qw/capture_exec/; my ($stdout, $stderr) = capture_exec( 'command', 'with', 'args' );

      -xdg

      Code written by xdg and posted on PerlMonks is public domain. It is provided as is with no warranties, express or implied, of any kind. Posted code may not have been tested. Use of posted code is at your own risk.

Re: Capturing STDERR using IO::Handle
by ysth (Canon) on Nov 26, 2007 at 09:11 UTC
    You are suffering from a little confusion. The fdopen method doesn't associate some other filehandle with your IO::Handle; it's the other way around: after the fdopen, your $stderr will be associated with whatever STDERR was before (only with the incorrect 'r' mode).

    To capture STDERR of a system call, you'd want the stderr output to go to some temporary file and then (rewind if necessary and) read from the file.

Re: Capturing STDERR using IO::Handle
by phio (Acolyte) on Nov 26, 2007 at 11:44 UTC
    I think you can get what you need here. Also, you can find some other examples in the 'qx' section in perlop
Re: Capturing STDERR using IO::Handle
by educated_foo (Vicar) on Nov 26, 2007 at 14:04 UTC
    Perlfunc has an example that does exactly what you want under open.

      I don't see anything of the kind. Could you be more specific, please?

      Remember tie(*HANDLE, ...) and open(..., \$var) don't produce system file handles, so they can't be inherited by child processes.

        Here is a script that saves, redirects, and restores STDOUT and STDERR using various methods:
        #!/usr/bin/perl open my $oldout, ">&STDOUT" or die "Can't dup STDOUT: $!"; open OLDERR, ">&", \*STDERR or die "Can't dup STDERR: $!"; open STDOUT, '>', "foo.out" or die "Can't redirect STDOUT: $!" +; open STDERR, ">&STDOUT" or die "Can't dup STDOUT: $!"; select STDERR; $| = 1; # make unbuffered select STDOUT; $| = 1; # make unbuffered print STDOUT "stdout 1\n"; # this works for print STDERR "stderr 1\n"; # subprocesses too open STDOUT, ">&", $oldout or die "Can't dup \$oldout: $!"; open STDERR, ">&OLDERR" or die "Can't dup OLDERR: $!"; print STDOUT "stdout 2\n"; print STDERR "stderr 2\n";
        It would be straightforward to then slurp in the contents of the saved-off files. Or did I misunderstand the question?
Re: Capturing STDERR using IO::Handle
by DrHyde (Prior) on Nov 26, 2007 at 11:44 UTC
      from Tie::STDERR docs:

      The module will catch all output, including your explicit prints to STDERR... However, if you run external command (system, ``), stderr output from that process won't be caught.

      So, it is not what the OP is looking for!

Re: Capturing STDERR using IO::Handle
by Sidhekin (Priest) on Nov 26, 2007 at 15:48 UTC

    This can't be done with simple ties.

    It can be done with Test::Trap, though. :) (Shameless as ever ....)

    use Test::Trap qw/ :flow:stderr(systemsafe) /; # or similar trap { system 'command', 'with', 'args' }; my @logmessages = split /\n/, $trap->stderr; # chomped! or season to t +aste

    Not exclusively for use in test scripts, indeed. :)

    print "Just another Perl ${\(trickster and hacker)},"
    The Sidhekin proves Sidhe did it!