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?
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.
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);
copy STDOUT to another filehandle. open (my $STDOLD, '>&', STDOUT); # redirect STDOUT to log. txt open (STDOUT, '>>', "$ARGV[1]".
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); }
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 ...
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With