http://www.perlmonks.org?node_id=245900

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

If I have a Perl script which calls various packages that in turn spit stuff out to STDERR (using "warn"), how do I capture STDERR into an array within the script? (note: this is within the script, not for some system call or for a script called on the command line..)

Redirecting STDERR to a file file is all very well, but there must be a better solution that redirecting STDERR to a random file and then reading the contents of the file in...



-- Ian Stuart
A man depriving some poor village, somewhere, of a first-class idiot.

Replies are listed 'Best First'.
Re: capturing STDERR within a script
by davorg (Chancellor) on Mar 26, 2003 at 11:45 UTC

    You can probably use IO::Scalar to tie STDERR to a scalar variable. Something like this (untested):

    use IO::Scalar; my $stderr; tie *STDERR, 'IO::Scalar', \$stderr;

    Now anything written to STDERR will end up in $stderr.

    --
    <http://www.dave.org.uk>

    "The first rule of Perl club is you do not talk about Perl club."
    -- Chip Salzenberg

Re: capturing STDERR within a script
by bart (Canon) on Mar 26, 2003 at 12:28 UTC
    If all you want is capture the warnings, setting $SIG{__WARN__} might do... STDERR doesn't allow itself to be tied so easily, especially for system messages.
    my @warnings; $SIG{__WARN__} = sub { push @warnings, shift; };
Re: capturing STDERR within a script - Solved.
by kiz (Monk) on Mar 26, 2003 at 12:44 UTC

    The solution is, as has been pointd out, to redefine the WARN signal handler.

    To redirect warnings for the command

    my $allofit = buildData(\%Edina::SSR::Common::everything);
    I simply created a subroutine:
    sub makeData { my @warns = (); local $SIG{__WARN__} = sub { push @warns, @_; }; my $allofit = buildData(\%Edina::SSR::Common::everything); return ($allofit, \@warns); }
    and called it with:
    my ($allofit, $error) = makeData();
    then the value of $error->... can be tested for any warnings



    -- Ian Stuart
    A man depriving some poor village, somewhere, of a first-class idiot.
Re: capturing STDERR within a script
by RhetTbull (Curate) on Mar 26, 2003 at 13:18 UTC
    My favorite way of doing this is to use Filter::Handle. The following snippet that should do what you want (that is, send all STDERR output to an array called @errors).
    #!/use/bin/perl use strict; use warnings; use Filter::Handle qw(subs); my @errors; #global to hold the output of STDERR sub filter_errors { #sub for Filter::Handle #copies everything sent to STDERR to global @errors #and then sends it on to STDERR local $_ = "@_"; push @errors, $_; $_; } #set up the filtering with Filter::Handle Filter \*STDERR, \&filter_errors; #test it print "Hello world\n"; print STDERR "This is my first error\n"; print "Goodbye world\n"; print STDERR "This is my second and final error\n"; UnFilter \*STDERR; #unfilter the output now that we're done if (@errors) { print "I got the following errors:\n"; print " $_" foreach (@errors); }
      That doesn't seem to want to work for me. It doesn't do a thing. Messages printed to STDERR still get printed, and @errors remains empty.

      If anybody feels like enlightening me to what's going on, please do.

        Hmm, not sure what the problem is. Here's what it does on my system. I've used this technique several times in the past with good success.
        $ perl -v This is perl, v5.6.1 built for MSWin32-x86-multi-thread (with 1 registered patch, see perl -V for more detail) Copyright 1987-2001, Larry Wall Binary build 631 provided by ActiveState Tool Corp. http://www.ActiveS +tate.com Built 17:16:22 Jan 2 2002 $ perl pmerr.pl Hello world This is my first error Goodbye world This is my second and final error I got the following errors: This is my first error This is my second and final error
        Instead of

        $_;

        as the last line of the sub, try

        ()

        It has to do with the return value of the sub, which is $_ in this case, and need to be an empty list if you want no output. The return value of the sub needs to be a list suitable for processing by the original file handle. In this case silence is an empty list.

        If all you want to do is capture the output, not process it, use the code from the Filter::Handle module

        Filter \*STDERR, sub {push @DATA, "@_"; ()};

        noisy stuff occurs...

        UnFilter \*STDERR;

        @DATA now holds all the noise emitted on STDERR.

        What version of perl are you using? I just tried the above example with perl 5.8.0 on cygwin and it displayed the behavior that you stated -- @errors was empty. This is the first time I've noticed this. A different Filter::Handle snippet caused 5.8.0 to coredump. I wonder if it's a bug in perl or a bug in Filter::Handle that broke with perl 5.8.0. Anyone else have any luck running Filter::Handle with perl >= 5.8.0?
Re: capturing STDERR within a script
by SysApe9000 (Acolyte) on Mar 26, 2003 at 16:47 UTC
    close STDERR; pipe(IN,STDERR); warn "test"; close STDERR; print "X:".(<IN>);'

    X:test at /tmp/ps line 3.

    Make sure you close STDERR before reading from it, it hung when I did (<IN>) without first closing it. You can always re-open it before going on.

    I expected that if you did something like system("find /xfkjkj") that the error message from find would also end up in IN, but it didn't. It is possible to do this, but I think you'd have to close STDIN or STDOUT first in addition to STDERR.

    This has nothing to do with perl and everything to do with UNIX. When you close STDERR you are closing file descriptor 2. Then next time you open a file it will use file descriptor 2. So, when we call pipe() the input side of the pipe gets descriptor 2 and the output gets another descriptor. Now, perl writes it's warnings to STDERR, not necessarily fd 2, so everything happens as expected. But a sub-process (as created by system()) writes to fd 2 (which doesn't help us here.)

    But perhaps your not on UNIX and pipe won't even work? I'll have to assume you are as I know little about other platforms.

    The mini-example I posted should work and doesn't involve creating a file, but in reality you could just use a temp file and accomplish the same thing. (A temp file should work on any platform as well.)