Anonymous Monk has asked for the wisdom of the Perl Monks concerning the following question:
I need your wisdom on nesting the rebinding of STDERR to a string buffer (one per nesting level). The communication is all within the same perl program. This is not about capturing STDERR from a system() call or a Pipe.
(Example program at the end of this text. Look for handleOuter() below).
Context: I have to capture the error output of a Perl module in a string buffer, which is already redirecting the error output of a third party module in order to handle the errors. The inner module follows the example given in the perlopentut man page and my outer code is following the same pattern.
The code tries to do a nested redirection of STDERR. It stores the prior buffers handle in an hash object. The outer code redirects STDERR to an $outerbuffer around the calls to the first module, which then redirects STDERR to an $innerbuffer again to capture the errors of some other module which prints on STDERR in case of errors.
It returns to the inner module which restores the binding and reacts to the errors in the $innerbuffer and writes its errors to STDERR and returns to the outer code. The outer code then restores its binding of STDERR and expects the error output or the inner module in $outerbuffer.
Problem: When the inner wrapper restores the binding for STDERR the prior binding of the outer STDERR to $outerbuffer is not restored. The text written by the inner module to STDERR after restoring the binding is not showing up in the $outerbuffer.
Question: How can I recursively rebind and restore STDERR? How can I rebind/restore STDERR in 'restore()' below? Is there a better way to save the prior handle? (Look for 'WHAT TO DO HERE INSTEAD' in the code.)
Pseudo code: redirect STDERR to $outerbuffer in the classic way of perlopentut call inner module: print STDERR "INNER BEFORE CAPTURE" # go to $outerbuffer redirect STDERR to $innerbuffer in the classic way of perlopentut call code which reports errors on STDERR eg: "FIRST CAPTURE" # above output goes to $innerbuffer restore STDERR of $innerbuffer in the classic way of perlopentut # From here on printing to STDERR should go to $outerbuffer again # But it does not. code handling $innerbuffer ... print STDERR "inner past restore" # should go to $outerbuffer (is + missing) print STDERR "outer past call" # should go to $outerbuffer (is missing +) restore STDERR of $outerbuffer in the classic way of perlopentut # From here on STDERR goes to the standard file handle (fd=2) #handleOuter() is expected to produce: #################### #handleOuter() is expected to produce: OUTER BEFORE CAPTURE INNER BEFORE CAPTURE INNER AFTER RESTORE BUFFER (inner): >>FIRST CAPTURE<< OUTER AFTER RESTORE BUFFER (outer): >>OUTER BEFORE CALL\ninner past restore\nouter past call<< INNER BEFORE CAPTURE INNER AFTER RESTORE BUFFER (inner): #>>FIRST CAPTURE<< OUTER AFTER RESTORE BUFFER (outer): #>>INNER BEFORE CAPTURE\ninner past restore<<
Here comes the wrapper module embedded in a full program.
#!/usr/bin/perl # Example code from 'perldoc -f open' # # Redirect standard stream STDERR to a buffer and then # restore the standard stream # With extension of nested redirections (which does not work!) ############################################################### package WrapSTDERR; use strict; use warnings; use Carp; use Data::Dumper; sub capture; sub restore; sub new { my $proto = shift; my $class = ref($proto) || $proto; my $self = { 'buffer' => "", 'STREAM' => undef, 'state' => 'closed' }; $self = bless $self, $class; return $self; } sub DESTROY { my $self = shift; if ($self->{'state'} eq 'bound') { $self->restore(); } } sub capture { my $self = shift; if ($self->{'state'} eq 'bound') { confess "Cannot bind STDERR again while it is bound," ." use another wrapper object."; } # duplicate STDERR filehandle in $self->{'STREAM'} open( $self->{'STREAM'}, ">&STDERR") ## no critic or die "Failed to save STDERR"; ## WHAT TO DO HERE INSTEAD? ## How to save the previous stream for restore? ## Can I find out whether STDERR is already bound to a string buffer # and save the ref to the buffer for rebinding on restore? $self->{'STREAM'}->print(""); # get rid of silly warning message close STDERR; # required for open open( STDERR, '>', \$self->{'buffer'} ) or die "Cannot open STDERR +: $!"; STDERR->autoflush(); $self->{'state'} = 'bound'; return $self; # for chaining with new } sub restore { my $self = shift; if (! $self->{'state'} eq 'bound') { confess "Cannot restore STDERR when it is not bound."; } close STDERR; # remove binding to string buffer # rebind STDERR to previous handle open( STDERR, ">&", $self->{'STREAM'} ) or die "Failed to restore STDERR"; ## WHAT TO DO ABOVE INSTEAD? ## Can I get the buffer to rebind from the STDERR handle in capture an +d save it? ## How to restore the binding to the buffer of the previous stream (if + there is one)? $self->{'STREAM'}->close(); $self->{'STREAM'}= undef; $self->{'state'} = 'closed'; my $data = $self->{'buffer'}; return $data; } 1; ###################################################################### package main; sub handleInner { print "INNER BEFORE CAPTURE\n"; my $innerCapture = WrapSTDERR->new()->capture(); print STDERR "FIRST CAPTURE\n"; # to innerbuffer, works my $buffer = $innerCapture->restore(); chomp $buffer; print "INNER AFTER RESTORE\n"; print STDERR "inner past restore\n"; # above goes to console or the outerbuffer, # it fails for outerbuffer when called from handleOuter print "BUFFER (inner): \n#>>$buffer<<\n"; } sub handleOuter { print "OUTER BEFORE CAPTURE\n"; my $outerCapture = WrapSTDERR->new()->capture(); print STDERR "OUTER BEFORE CALL\n"; # to outerbuffer handleInner(); print STDERR "outer past call\n"; # to outerbuffer # (It does not go to the buffer in $outerCapture, # which is the topic of this question) my $buffer = $outerCapture->restore(); chomp $buffer; print "OUTER PAST RESTORE\n"; print "BUFFER (outer): \n#>>$buffer<<\n"; } handleInner(); #prints this: # INNER BEFORE CAPTURE # INNER AFTER RESTORE # INNER PAST RESTORE # BUFFER (inner): #>>FIRST CAPTURE<< print "####################\n"; handleOuter(); #prints this: # OUTER BEFORE CAPTURE # INNER BEFORE CAPTURE # INNER AFTER RESTORE # BUFFER (inner): >>FIRST CAPTURE<< # OUTER AFTER RESTORE # BUFFER (outer): #>>OUTER BEFORE CALL<< # Above line is not correct. #handleOuter() is expected to produce: # OUTER BEFORE CAPTURE # INNER BEFORE CAPTURE # INNER AFTER RESTORE # BUFFER (inner): >>FIRST CAPTURE<< # OUTER AFTER RESTORE # BUFFER (outer): #>>OUTER BEFORE CALL\ninner past restore\nouter past call<<
|
---|
Replies are listed 'Best First'. | |
---|---|
Re: Nested redirect for STDERR to a string buffer within a perl program
by ikegami (Patriarch) on Apr 30, 2025 at 16:22 UTC | |
by Anonymous Monk on May 01, 2025 at 15:11 UTC | |
Re: Nested redirect for STDERR to a string buffer within a perl program
by tybalt89 (Monsignor) on May 01, 2025 at 14:40 UTC | |
by Anonymous Monk on May 01, 2025 at 15:20 UTC | |
by NERDVANA (Priest) on May 02, 2025 at 00:08 UTC |