Beefy Boxes and Bandwidth Generously Provided by pair Networks
Just another Perl shrine
 
PerlMonks  

Capturing STDERR (this is not a FAQ)

by mikosullivan (Novice)
on Feb 10, 2015 at 04:31 UTC ( [id://1116137]=perlquestion: print w/replies, xml ) Need Help??

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

Insert standard disclaimer that, honest, I've tried to find the answer to this question on my own and haven't been able to find it.

This isn't your usual "How to I redirect STDERR?" question. I have a specific set of requirements that haven't been met by any of the techniques I can find. Those requirements are as follows:

  1. The technique captures STDERR from both Perl commands (e.g. print STDERR "whatever\n") and external commands (e.g. system 'ls file-that-does-not-exist.txt').
  2. STDERR is directed to a tied file handle. That handle does whatever it does with that data. The point here isn't what it does, just that the data gets sent to the handle.
  3. The life of the tied handle is associated with the life of an object. That object is referenced by one or more lexical variables. When the object goes out of scope then STDERR is returned to its previous state. This is not actually an absolute requirement, but it would be way prefered.
  4. The scope of the object might not be a single lexical scope, so solutions involving local probably won't work.
  5. I only care if it works in Un*x.
So, code might look something like this:
sub get_stderr {
   my ($capturer);
   $capturer = CAPTURE::Class->new();
   return $capturer;
}

# in some other file:
my $capturer = get_stderr();

# Run commands that might output to STDERR.
print STDERR "STDERR in perl\n";
system 'ls file-that-does-not-exist.txt';

# later...
undef $capturer;
print STDERR "stuff that doesn't go to capturer\n";
The closest I've been able to come is to capture STDERR, including stuff from external commands, like this:
#!/usr/bin/perl -w
use strict;

# redirect STDERR to a log file
open (STDERR, '>', './log.txt') or die "could not open STDERR: $!\n";

# print to STDERR in Perl
print STDERR "STDERR in perl\n";

# run an external command that outputs something to STDERR
system 'ls file-that-does-not-exist.txt';
That's nice as far as it goes, but it doesn't quite meet my needs. First, STDERR is only sent to a file. I can't figure out how to send it do a tied object. I have found ways to send STDERR to a tied object, but those techniques don't capture STDERR from external commands. Second, I can't figure out how to restore STDERR to its previous state once I'm done with the tied handle.

Much thanks, and looking forward to your help.

Replies are listed 'Best First'.
Re: Capturing STDERR (this is not a FAQ)
by Tux (Canon) on Feb 10, 2015 at 07:15 UTC

    Preview != create :(

    Capture::Tiny is indeed the way to go, with a small gotcha:

    $ cat test.pl use 5.18.2; use warnings; use Capture::Tiny "capture"; my ($out, $err, $ext) = capture { # Local scope print STDERR "Output to STDERR\n"; warn "Warn!\n"; system "ls /tmp/bjfgerfbv"; qx{ls /tmp/jhgbejgrbe}; }; # Previous scope print STDERR "Hello world\n"; system "ls /tmp/foobar2"; print STDERR "OUT: $_\n" for split m/\n/ => $out; print STDERR "ERR: $_\n" for split m/\n/ => $err; $ perl test.pl Hello world /bin/ls: cannot access /tmp/foobar2: No such file or directory ERR: Output to STDERR ERR: Warn! ERR: /bin/ls: cannot access /tmp/bjfgerfbv: No such file or directory ERR: /bin/ls: cannot access /tmp/jhgbejgrbe: No such file or directory

    The gotcha is that STDERR inside capture is not unbuffered, so if your last print STDERR "foo"; does not have a trailing newline, it is lost. That might change in a newer release (at least I hope)


    Enjoy, Have FUN! H.Merijn
Re: Capturing STDERR (this is not a FAQ)
by ikegami (Patriarch) on Feb 10, 2015 at 05:39 UTC

    The child process cannot access variables or call subroutines in your process. So your second criteria cannot be met. You will need to have the child write to a pipe from which you will read and write to the tied handle.

    use IPC::Open3 qw( open3 ); open(local *CHILD_STDIN, '<', '/dev/null') or die $!; my $pid = open3( '<&CHILD_STDIN', '>&STDOUT', local *CHILD_STDERR, 'ls file-that-does-not-exist.txt', ); while (<CHILD_STDERR>) { print $tied_fh $_; } waitpid($pid, 0);
    IPC::Open3 is quite low level. IPC::Run3 and/or IPC::Run can surely make this much simpler.
      OK, except here's where I'm unclear. This command sorta does what I want:
      open (STDERR, '>', './log.txt')
         or die "could not open STDERR: $!\n";
      
      It captures STDERR from child processes without any calls to system or backticks or whatever having to be called in a special way. The problem is that it only redirects to a file. Isn't there some way to redirect it to a subroutine or file handle instead?
        It captures STDERR from child processes without any calls to system or backticks or whatever having to be called in a special way.

        That's because child processes inherit their stdin/stdout/stderr from their parent processes.

        But file handles are OS level entities, not language specific entities. A tied filehandle is a perl-level entity; it cannot be inherited by a C program.


        With the rise and rise of 'Social' network sites: 'Computers are making people easier to use everyday'
        Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
        "Science is about questioning the status quo. Questioning authority". I'm with torvalds on this
        In the absence of evidence, opinion is indistinguishable from prejudice. Agile (and TDD) debunked

        This sounds promising.

        From perl's core documentation: open
        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";
        Bill

        Isn't there some way to redirect it to a subroutine

        The child process cannot access variables or call subroutines in your process. You will need to have the child write to a pipe from which you will read, then you can pass what you read to a subroutine.

        or file handle instead?

        huh? "Redirecting data sent to a file handle to a file handle" makes no sense.

        A reply falls below the community's threshold of quality. You may see it by logging in.
Re: Capturing STDERR (this is not a FAQ)
by Anonymous Monk on Feb 10, 2015 at 06:52 UTC

    The Capture::Tiny docs indicate that it should work at least with some tied filehandles, maybe you could use that, or at least look into its source code to help you figure out to do what you want?

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://1116137]
Approved by GrandFather
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others browsing the Monastery: (2)
As of 2024-04-24 17:51 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found