Reputation: 38949
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?
Upvotes: 27
Views: 20201
Reputation: 13942
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 ...
Upvotes: 11
Reputation: 385789
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);
}
Upvotes: 38
Reputation: 39158
-
is magic. Follow the convention or you violate the principle of least surprise.
use autodie qw(:all);
use Getopt::Long qw(GetOptions);
GetOptions(\my %opt, 'out=s') or die 'usage';
die 'usage' unless $opt{out};
open my $handle, ">$opt{out}";
perl foo -o blahblah # opens file blahblah for writing
perl foo -o - # goes to STDOUT
Related: Are there reasons to ever use the two-argument form of open(...) in Perl?
Upvotes: 7
Reputation: 38949
Nevermind. Figured it out. Throwing in an ampersand does the trick. Full working code is:
open( OUTPUT, ( $output ? ">$output" : ">&STDOUT" ) );
Upvotes: 5
Reputation: 32576
You could always just write the regular output to STDOUT
and your debug statements to STDERR
. Then if the user wants the output to go to a file they can just redirect STDOUT to the file on the command line.
Upvotes: 3