Beefy Boxes and Bandwidth Generously Provided by pair Networks
There's more than one way to do things
 
PerlMonks  

"tee"ing my own stdout/stderr

by conrad (Beadle)
on Jun 07, 2010 at 14:14 UTC ( [id://843501]=perlquestion: print w/replies, xml ) Need Help??

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

Dear monks,

I've a script (on Linux) whose output I'd like to automatically record to a file at the same time as display on the terminal. The traditional approach to this is along the lines of

open STDOUT, "|tee '$outfile'" or die ...

This doesn't seem to interact brilliantly with some SIGINT handling I'm doing though. I was looking for alternative methods and found an old module Local::TeeOutput (not on CPAN), which looks great but seems a bit long in the tooth. Using it caused some deep weirdness to crop up elsewhere (Module::Pluggable, which I'm also using, started to die looking for xyz::SUPER) so I continued looking. I've now come up with the following, which appears to work, but I have to admit my understanding of file globs etc is not deep enough. I'd like to know whether you think it's a reasonable solution or will come back to bite me in subtle but horrible ways. All I'm seeking to record is my Perl process's stdout/stderr, so I don't mind that this doesn't record subprocesses' output (but would be interested to hear of solutions that would):

use IO::Tee; my $outfile = "test.out"; # Traditional approach: #open STDOUT, "|tee '$outfile'" or # die "Couldn't tee STDOUT to log file '$outfile': $!"; # Possible alternative? open STDOUT2, ">&=STDOUT" or die "Failed to alias STDOUT: $!"; *STDOUT = IO::Tee->new(\*STDOUT2, ">$outfile") or die "Failed to tee to aliased STDOUT and '$outfile': $!"; print "Test 1.\n"; system("echo Test 2."); # Don't really care that this isn't recorded

(Note before the above glob assignment, I tried my $tee_out = IO::Tee->new(...); open STDOUT, ">&", $tee_out or die ...;, but that just didn't work: it stringified $tee_out and used it as a filename.)

(Also note that if I don't alias STDOUT to STDOUT2 first, and instead assign a tee to STDOUT to STDOUT, it segfaults - Local::TeeOutput seems to cope with that kind of possibly-on-the-face-of-it recursive assignment though.)

I've already found one way this breaks: things like run3() don't have a valid STDOUT file descriptor to save when invoking children. Further comments/suggestions welcome though.

Thanks for your time,
Conrad

Replies are listed 'Best First'.
Re: "tee"ing my own stdout/stderr
by ikegami (Patriarch) on Jun 07, 2010 at 16:45 UTC

    The tee created by open STDOUT, '|-', tee => $outfile will capture the output of children too, but it won't work well with a custom SIGINT handler.

    The tee created by IO::Tee will work with a custom SIGINT handler, but it won't capture the output of executed children.

    It sounds like you want to capture the output of children and use a custom SIGINT. Based on my testing, the following does that:

    open(STDOUT, '|-', bash => ( -c => 'trap "" INT ; tee -- "$0"', $outfile, ) ) or die;

    Update: Since this is a common problem, tee actually provides a fix for it in the form of -i:

    open(STDOUT, '|-', tee => ( '-i', '--', $outfile ) ) or die;

      Rock. Yeah, that's exactly the issue. As I mentioned in my reply to salva above, it also seems to be possible to get around the problem by setting Perl's $SIG{INT} = 'IGNORE' before forking whichever processes will do your tee'ing, and setting your custom handler afterward. As ever, TMTOWTDI :)

      Thanks!

Re: "tee"ing my own stdout/stderr
by salva (Canon) on Jun 07, 2010 at 14:40 UTC
    use File::Tee qw(tee); tee(STDOUT, '>', $outfile) or die ...;

      Thanks; I originally didn't look at that one 'cos of its low 0.05 version number. It has the same SIGINT problems I had as with the "|tee" solution (broken pipe errors rather than getting SIGINTs), but I've now worked out how to solve that: I was setting up the signal handler before starting the tees, but it occurred to me that maybe ignoring SIGINT before the tee and setting the handler up afterward would cause the children created by the tee to survive. This works. So, for example:

      use POSIX; use File::Tee 'tee'; my $ctrl_c; my $outfile = "test.out"; my $errfile = "test.err"; $SIG{INT} = 'IGNORE'; tee(STDOUT, '>', $outfile) or die "Couldn't tee STDOUT to log file '$outfile': $!"; tee(STDERR, '>', $errfile) or die "Couldn't tee STDERR to log file '$errfile': $!"; $SIG{INT} = sub { $ctrl_c = 1; }; print "Testing 1.\n"; print STDERR "Test err 2.\n"; system("sleep 3 ; echo test 3; echo test 4 1>&2"); print "System: $?\n"; kill SIGINT, $$ if ($? & 127) == SIGINT; die "Interrupted!" if $ctrl_c; print "Done!\n";

      Appears to do the job - cool, thanks!

        I have released a new version of File::Tee (0.06) that has the ignore-sigint behavior built in.
Re: "tee"ing my own stdout/stderr
by jdporter (Paladin) on Jun 07, 2010 at 15:14 UTC

    You could try my solution here: IO::MultiHandle - Operate on multiple file handles as one

    Update: But I don't know if it won't have the same SIGINT sensitivities as the other things you've tried.

    What is the sound of Windows? Is it not the sound of a wall upon which people have smashed their heads... all the way through?

Log In?
Username:
Password:

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

How do I use this?Last hourOther CB clients
Other Users?
Others imbibing at the Monastery: (5)
As of 2024-04-25 13:37 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found