Beefy Boxes and Bandwidth Generously Provided by pair Networks
Do you know where your variables are?
 
PerlMonks  

Generic and nestable STDIN/STDOUT capture routine

by hanenkamp (Pilgrim)
on May 21, 2005 at 20:34 UTC ( [id://459275]=CUFP: print w/replies, xml ) Need Help??

This is probably going overboard, but I have an idea for a comprehensive method for capturing STDIN/STDOUT. The goal is to capture all traffic, but be method agnostic. Also, I want to allow nested captures. The two general methods I know of for doing this involve dup'ing file handles and tieing file handles. Dup'ing is most useful when you want to capture all the output of a exec'd process. Tieing is generally useful when you need to capture output within Perl (e.g., IO::String, IO::Scalar, etc.). Oh, and the final requirement: it must work in Perl 5.6, so PerlIO is right out.

I should say that part of this solution is based upon knowledge primarily gained from the discussion in tieing STDIN & STDOUT using IO::Scalar for use inside an eval, Redirecting STDOUT from internal function with 5.6.1 restrictions, and Section 7.10 of Perl Cookbook, 2nd Ed by Christiansen and Torkington. I've also taken note of some modules designed to do something like this, such as IO::Capture, and of course IO::String and IO::Scalar. However, each of these falls short of my goals.

I've tested this code in a very limited fashion (i.e., this script), but I want to know if anyone sees any caveats to this solution. Is it sound? Do you like it? What would you change?

The capture_streams() subroutine is the interesting part. The rest is the test script.

Thanks.

#!/usr/bin/perl use strict; use warnings; use File::Temp 'tempfile'; use IO::String; use Symbol; sub capture_streams { my $in = shift; my $out = shift; my $code = shift; my $tie_in = UNIVERSAL::can($in, 'TIEHANDLE'); my $tie_out = UNIVERSAL::can($out, 'TIEHANDLE'); my ($save_in, $save_out); my ($save_in_fd, $save_out_fd); # Save/capture STDIN if ($tie_in) { $save_in = tied *STDIN; tie *STDIN, $in; } else { if (tied *STDIN) { $save_in = tied *STDIN; no warnings 'untie'; untie *STDIN; } $save_in_fd = gensym; open($save_in_fd, '<&STDIN'); open(STDIN, '<&='.fileno($in)); } # Save/capture STDOUT if ($tie_out) { $save_out = tied *STDOUT; tie *STDOUT, $out; } else { if (tied *STDOUT) { $save_out = tied *STDOUT; no warnings 'untie'; untie *STDOUT; } $save_out_fd = gensym; open($save_out_fd, '>&STDOUT'); open(STDOUT, '>&='.fileno($out)); } # Run code within captured handles my $result; if (wantarray) { my @array = $code->(@_); $result = \@array; } else { $result = $code->(@_); } # Restore STDOUT if ($tie_out) { if (defined $save_out) { tie *STDOUT, $save_out; } else { no warnings 'untie'; untie *STDOUT; } } else { open(STDOUT, '>&='.fileno($save_out_fd)); close($save_out_fd); if (defined $save_out) { tie *STDOUT, $save_out; } } # Restore STDIN if ($tie_in) { if (defined $save_in) { tie *STDIN, $save_in; } else { no warnings 'untie'; untie *STDIN; } } else { open(STDIN, '<&='.fileno($save_in_fd)); close($save_in_fd); if (defined $save_in) { tie *STDIN, $save_in; } } return wantarray ? @$result : $result; } sub test_it { my ($in, $out, $data) = @_; print $in $data; seek $in, 0, 0; capture_streams($in, $out, sub { while (<STDIN>) { tr/abcdefghijklmnopqrstuvwxyz/zyxwvutsrqponmlkjihgfedcba/; print $_; } }); seek $out, 0, 0; while (<$out>) { print; } } sub nested_test_it { my ($in, $in2, $out, $out2, $data) = @_; print $in $data; seek $in, 0, 0; capture_streams($in, $out, sub { while (<STDIN>) { print $in2 $_; } seek $in2, 0, 0; capture_streams($in2, $out2, sub { while (<STDIN>) { tr/abcdefghijklmnopqrstuvwxyz/zyxwvutsrqponmlkjihgfedc +ba/; print $_; } }); seek $out2, 0, 0; while (<$out2>) { print; } }); seek $out, 0, 0; while (<$out>) { print; } } my $data = join '', <STDIN>; eval { print "Test 0\n"; print '=' x 70, "\n"; test_it(IO::String->new, IO::String->new, $data); }; print STDERR $@ if $@; eval { print "\nTest 1\n"; print '=' x 70, "\n"; test_it(scalar(tempfile), scalar(tempfile), $data); }; print STDERR $@ if $@; eval { print "\nTest 2\n"; print '=' x 70, "\n"; nested_test_it( IO::String->new, IO::String->new, IO::String->new, IO::String->new, $data); }; print STDERR $@ if $@; eval { print "\nTest 3\n"; print '=' x 70, "\n"; nested_test_it( scalar(tempfile), IO::String->new, IO::String->new, scalar(tempfile), $data); }; print STDERR $@ if $@; eval { print "\nTest 4\n"; print '=' x 70, "\n"; nested_test_it( IO::String->new, scalar(tempfile), scalar(tempfile), IO::String->new, $data); }; print STDERR $@ if $@; eval { print "\nTest 5\n"; print '=' x 70, "\n"; nested_test_it( scalar(tempfile), scalar(tempfile), scalar(tempfile), scalar(tempfile), $data); }; print STDERR $@ if $@;

Log In?
Username:
Password:

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

How do I use this?Last hourOther CB clients
Other Users?
Others avoiding work at the Monastery: (2)
As of 2024-04-20 06:18 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found