Home > Software engineering >  How to open a filehandle with an existing variable in perl?
How to open a filehandle with an existing variable in perl?

Time:05-10

In my Perl script, I would like to process lines from either STDIN or a given file, if specified, as common with Linux/UNIX command line utilities.

To this end, I have the following section in my script (simplified for the post):

use strict;
use warnings;

my $in = \*STDIN;
open $in, '<', $ARGV[0] or die if (defined $ARGV[0]);
print while (<$in>);

Essentially, I define $in to be a reference to the STDIN typeglob, so normally, if no argument is specified, the script does print for each line of <STDIN>. So far, so good.

If $ARGV[0] is defined however, I would like to read lines from that. That is what the second meaningful line purports to do. However, it seems that no lines are processed when ran with an argument.


I noticed that after my conditional call to open, $in does not change, even when I expect it to;

my $in = \*STDIN;
print $in, "\n";

open $in, '<', $ARGV[0] or die if (defined $ARGV[0]);
print $in, "\n";

yields

GLOB(0xaa08b2f4f28)
GLOB(0xaa08b2f4f28)

even when $ARGV[0] is defined. Does open not work when the first variable passed is already referring to a filehandle?

The relevant documentation does include the following

About filehandles

The first argument to open, labeled FILEHANDLE in this reference, is usually a scalar variable. (Exceptions exist, described in "Other considerations", below.) If the call to open succeeds, then the expression provided as FILEHANDLE will get assigned an open filehandle. That filehandle provides an internal reference to the specified external file, conveniently stored in a Perl variable, and ready for I/O operations such as reading and writing.

Based on this alone, I do not see why my code would not work.

CodePudding user response:

That's precisely what the null filehandle <> does

Input from <> comes either from standard input, or from each file listed on the command line.

So all you need is

while (<>) { 
    ...
}

(see the rest of what docs say about it)

Another, in some cases safer option, is to use a double diamond bracket

while (<<>>) { } 

Using double angle brackets inside of a while causes the open to use the three argument form (with the second argument being <), so all arguments in ARGV are treated as literal filenames (including "-"). (Note that for convenience, if you use <<>> and if @ARGV is empty, it will still read from the standard input.)

(again, please see the rest of what docs say)


Added

As for the direct question -- yes, one can reassign a filehandle just as you try to do.

But the ... or die if ... syntax is wrong as one cannot chain conditionals like that.

However, I cannot reproduce the shown behavior as your code actually works for me (on 5.16 and 5.30 on Linux). My best guess then is that such code results in an "undefined behavior" and we get unpredictable and inconsistent behaviors.

While <<>> does what is asked, if you do need to have a filehandle at a minimum can fix that by adding parenthesis

(open $in, '<', $ARGV[0] or die $!) if defined $ARGV[0];

But I'd recommend writing a nice and readable test instead of cramming it into one statement.

CodePudding user response:

You want to use the magical ARGV file handle, which does exactly what you want.

And the safest way to read from is is the following:

while (<<>>) {
   ...
}

You were going for something like this:

my $in_fh;
if ( @ARGV ) {
  open( $in_fh, "<", $ARGV[0] )
     or die( "Can't open `$ARGV[0]`: $!\n" );
} else {
   $in_fh = \*STDIN;
}

while (<$fh_in>) {
   ...
}

However, unlike unix tools, this only reads from the first file provided. Using the first solution reads from each file provided.

  •  Tags:  
  • perl
  • Related