Subroutine for piping stdout and stderr into file
Asked Answered
C

2

2

If I have a program called simplecalc.pl:

use v5.10; 
use warnings;
use strict;

# SIMPLE CALCULATOR
# Usage: simplecalc.pl <n1> <n2> [<verbose> <logswitch>]
# Example Usage:
# normal usage      : simplecalc.pl 4 2  
# verbose usage     : simplecalc.pl 4 2 1
# support-case usage: simplecalc.pl 4 2 0 1

my($OK,$UNKNOWN)=(0,3);
my($filename, $endmsg, $exit) = ('my.log', undef, undef);
my($n1, $n2, $DEBUG, $GET_SUPPORT_FILE) = @ARGV;

# Handling of the support-file starts here ===============================
*ORIGINAL_STDOUT = *STDOUT;

if ($GET_SUPPORT_FILE) {
    $DEBUG = 1;
    $endmsg = "support-info sucessfully written into $filename";
    $exit = $UNKNOWN;
    # redirect stdout and stderr
    open my $stdout_txt, '>>:utf8', "$filename";
    *STDOUT = $stdout_txt;
    open STDERR, '>&STDOUT';
} else {
    $endmsg = "Finnished calculation - good bye.";
    $exit = $OK;
}

END {
    select *ORIGINAL_STDOUT;
    say $endmsg;
};
# end of support-file handling ============================================


say STDERR "INFO: got $n1 and $n2 from the commandline" if $DEBUG;
say "Hello, let me calc the quotient from $n1 trough $n2 for you ...";
my $quotient = $n1 / $n2;
say "Quotient: $quotient";

exit $exit;

Is there a way to put the lengthy handling of the support-file in a reusable way into a module? (The support-file is meant to be sent by the user to the program-maintainer.)

Note: The above solution also works for simplecalc.pl 4 0 0 1 which results in a division trough 0. Catching a die in any module used by the main-programm and write the die-msg into the support-file is an important feature.

Cenacle answered 23/4, 2018 at 10:46 Comment(3)
I don't see what you need -- under get_support it once redirects streams (and doesn't write anything?) but the other time it switches back. In the desired-module, I don't see what the write_support_file writes to that file (there is only the message for the restored STDOUT). Can you state the purpose?Adel
You are right, the wording and layout of the question was anything else than optimal. I'll rewrite it using elements from @kjetil-s answer.Cenacle
Thank you, much clearer. I am still unsure of the overall purpose, please see my answer and let me know if neededAdel
A
2

I take the question to want to control redirection of both streams from a module.

Something like this basic example?

RedirectStreams.pm

package RedirectStreams;

use warnings;
use strict;

use Exporter qw(import);
our @EXPORT_OK = qw(redirect_streams restore_streams);

our ($stdout, $stderr) = (*STDOUT, *STDERR);

sub redirect_streams {
    my ($handle) = @_;
    *STDOUT = $handle;
    *STDERR = $handle;
}

sub restore_streams {
    *STDOUT = $stdout;
    *STDERR = $stderr;
}

1;

main.pl

use warnings;
use strict;

use RedirectStreams qw(redirect_streams restore_streams);

my $logfile = shift @ARGV  || 'streams.log';

say "Hello from ", __PACKAGE__;            
warn "WARN from ", __PACKAGE__;

open my $fh, '>', $logfile;
    
redirect_streams($fh);

say "\tHi to redirected";
warn "\tWARN to redirected";

restore_streams();
    
say  "Hi to STDOUT again";
warn "WARN in main again";

open my $fh_other, '>', 'other_' . $logfile;
redirect_streams($fh_other);
say  "STDOUT redirected to another";
warn "STDERR redirected to another";

close $_ for $fh, $fh_other;

Output on console is (aligned)

Hello from main
WARN from main      at ... line 18.
Hi to STDOUT again
WARN in main again  at ... line 29.

while the file streams.log has

        Hi to redirected
        WARN to redirected at ... line 24.

and other_streams.log has its two lines. (Tabbed to spot easily if they wind up on console.)

The responsibility for managing filehandles in this example rests on the caller.

This should be completed with all manner of error checking, options in subs (redirect only one stream, or each to its own file, etc), and probably a few more convenience routines.


Note that our $stdout = *STDOUT creates an alias.

Another way to preserve STDOUT for later restoration is to duplicate it,

open my $stdout, '>&', STDOUT;

This creates an independent filehandle, made as a duplicate of STDOUT (what is all we need here), which is unaffected by changes (or closure) of STDOUT. See this post for an example use.

Adel answered 24/4, 2018 at 17:32 Comment(2)
I used your RedirectStreams in my example simplecalc.pl and it works just fine - thanks a lot! Regarding your question about the overall-purpose: I am delivering an application with many scripts -- all of them had these lengthy and hard to maintain code to allow the user to write a support-file. I wanted to have that in one place. Your example is now the starting-point to further centralize any aspects of that support-file.Cenacle
@Cenacle Great :) One other option is to have subs in the module open files themselves, so the caller just passes the filename. That needs a little more care, checks etc. Also, what this post shows may (or may not?) be of interest.Adel
L
3

I'm guessing you're looking for select which changes the default file handler in print and say. And END which is run right before the program ends.

use v5.10; use warnings; use strict;
my($OK,$UNKNOWN)=(1,0);

my($filename, $endmsg, $exit) = ('my.log', 'OK', $OK);
END { say $endmsg }
my $DEFAULT_FH=select;  #select returns current default file handler (often STDOUT)
if( rand()<0.5){        #half the time, for test
    open my $FH, '>>:utf8', $filename or die; #append
    $endmsg = qq{support-info successfully written into $filename};
    $exit = $UNKNOWN;
    select $FH;
}

print "print something\n";
say   "say something more"; #same as print except \n is added

if(1){
    select $DEFAULT_FH;     #or just:  select STDOUT
}
exit $exit;
Longplaying answered 23/4, 2018 at 11:37 Comment(1)
Thanks for taking the time to guess what I need! I have rewritten the question above using your hint about END blocks.Cenacle
A
2

I take the question to want to control redirection of both streams from a module.

Something like this basic example?

RedirectStreams.pm

package RedirectStreams;

use warnings;
use strict;

use Exporter qw(import);
our @EXPORT_OK = qw(redirect_streams restore_streams);

our ($stdout, $stderr) = (*STDOUT, *STDERR);

sub redirect_streams {
    my ($handle) = @_;
    *STDOUT = $handle;
    *STDERR = $handle;
}

sub restore_streams {
    *STDOUT = $stdout;
    *STDERR = $stderr;
}

1;

main.pl

use warnings;
use strict;

use RedirectStreams qw(redirect_streams restore_streams);

my $logfile = shift @ARGV  || 'streams.log';

say "Hello from ", __PACKAGE__;            
warn "WARN from ", __PACKAGE__;

open my $fh, '>', $logfile;
    
redirect_streams($fh);

say "\tHi to redirected";
warn "\tWARN to redirected";

restore_streams();
    
say  "Hi to STDOUT again";
warn "WARN in main again";

open my $fh_other, '>', 'other_' . $logfile;
redirect_streams($fh_other);
say  "STDOUT redirected to another";
warn "STDERR redirected to another";

close $_ for $fh, $fh_other;

Output on console is (aligned)

Hello from main
WARN from main      at ... line 18.
Hi to STDOUT again
WARN in main again  at ... line 29.

while the file streams.log has

        Hi to redirected
        WARN to redirected at ... line 24.

and other_streams.log has its two lines. (Tabbed to spot easily if they wind up on console.)

The responsibility for managing filehandles in this example rests on the caller.

This should be completed with all manner of error checking, options in subs (redirect only one stream, or each to its own file, etc), and probably a few more convenience routines.


Note that our $stdout = *STDOUT creates an alias.

Another way to preserve STDOUT for later restoration is to duplicate it,

open my $stdout, '>&', STDOUT;

This creates an independent filehandle, made as a duplicate of STDOUT (what is all we need here), which is unaffected by changes (or closure) of STDOUT. See this post for an example use.

Adel answered 24/4, 2018 at 17:32 Comment(2)
I used your RedirectStreams in my example simplecalc.pl and it works just fine - thanks a lot! Regarding your question about the overall-purpose: I am delivering an application with many scripts -- all of them had these lengthy and hard to maintain code to allow the user to write a support-file. I wanted to have that in one place. Your example is now the starting-point to further centralize any aspects of that support-file.Cenacle
@Cenacle Great :) One other option is to have subs in the module open files themselves, so the caller just passes the filename. That needs a little more care, checks etc. Also, what this post shows may (or may not?) be of interest.Adel

© 2022 - 2024 — McMap. All rights reserved.