Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How Do I Point a File Handle to STDOUT (or Another File Handle) In Perl?

Tags:

I want to make a quick script that writes to a file if a file is given, or stdout if no file is given. It would be much easier for me to do this if I could start the script by pointing a new file handle, OUTFILE, to whichever of these is appropriate.

I know I can accomplish what I want by just pointing STDOUT to my output file when appropriate, but I'd rather not do that as I don't want to confuse anyone using the script later and wondering why their print statements don't work. Also, I'd still like to use the occasional print statement myself for troubleshooting purposes, and never want this print to the output file.

All of this said, what I'm looking for is something along the lines of:
open( OUTFILE, ($output ? ">$output" : STDOUT );
except that doesn't work.

Ideas?

like image 479
Eli Avatar asked Jun 09 '11 17:06

Eli


People also ask

How do I redirect output to a file in Perl script?

The syntax for the command shell on Windows resembles Bourne shell's. open STDOUT, ">", "output. txt" or die "$0: open: $!"; open STDERR, ">&STDOUT" or die "$0: dup: $!"; to the beginning of your program's executable statements.

How do I pass a file handler in Perl?

To pass a file handle to a subroutine, use * in the subroutine prototype type to indicate a file handle parameter. But getting the file handle into something you can use is a little tricky. For that, you need a module called Symbol: use Symbol; sub print_it(*) { my $file_handle = qualify_to_ref(shift, caller);

How do I save an output to a file in Perl?

copy STDOUT to another filehandle. open (my $STDOLD, '>&', STDOUT); # redirect STDOUT to log. txt open (STDOUT, '>>', "$ARGV[1]".


2 Answers

Some ways (globs):

# Requires 2-arg open, unfortunately open(OUTPUT, "> ".($output || '-'))  or die; 

if ($output) {    open(OUTPUT, '>', $output) or die; } else {    *OUTPUT = *STDOUT; } 

if ($output) {    open(OUTPUT, '>', $output) or die; } else {    open(OUTPUT, '>&', \*STDOUT) or die; } 

Some ways (lexical var):

# Requires 2-arg open, unfortunately open(my $fh, "> ".($output || '-'))  or die; 

my $fh; if ($output) {    open($fh, '>', $output) or die; } else {    $fh = \*STDOUT; } 

my $fh; if ($output) {    open($fh, '>', $output) or die; } else {    open($fh, '>&', \*STDOUT) or die; } 

Some ways (other):

# Changes the default handle for <print "foo">. if ($output) {    open(OUTPUT, '>', $output) or die;    select(OUTPUT); } 
like image 120
ikegami Avatar answered Sep 24 '22 23:09

ikegami


For the reader's sake (since OP already "got it working"):

The Perl documentation, "perlopentut" (perldoc perlopentut) gives examples of opening an alternate filehandle directed to STDOUT, but it uses bareword filehandles, and the two-arg version of open, both of which are somewhat ancient. Damian Conway's book "Perl Best Practices" (as well as Perl::Critic, which is based on Damian's book) emphasizes using three arg opens and lexical filehandles (my $foo style filehandles). chromatic's "Modern Perl" also briefly mentions using lexical filehandles, and discourages barewords where practical. The three arg open is considered to be a safer form, as it limits exposure to shell injections. While the specific case here is low risk, it's still a good habit to default to the three-arg open.

It takes careful reading of the examples shown in Perl's open documentation (perldoc -f open), and a little interpretation to notice the correct placement of the ampersand character in opening a duplicate filahandle directed to STDOUT when using the three-arg version of open. One might errantly assume that this should work: open my $fh, '>', '&STDOUT' or die $!;, mostly because it looks like a format we're used to seeing (ampersand preceding a symbol name). But that syntax isn't what open needs, and we're used to seeing it in an entirely different context: certain subroutine calls.

The correct way to open a dupe to STDOUT with the three arg open is to combine the > and the & into the same second argument, and leave STDOUT bare, like this:

use Modern::Perl;  open my $fh, '>&', STDOUT or die "Bleah: $!";  say $fh 'Printing alternate filehandle "$fh" worked.'; say 'Implicitly printing to "STDOUT" also works.'; 

Or passing STDOUT to open as a reference to the typeglob:

open my $fh, '>&', \*STDOUT or ... 
like image 33
DavidO Avatar answered Sep 25 '22 23:09

DavidO