views:

566

answers:

4

I have a subroutine that takes a filehandle as an argument. How do I make a filehandle from a file path specified on the command line? I don't want to do any processing of this file myself, I just want to pass it off to this other subroutine, which returns an array of hashes with all the parsed data from the file.

Here's what the command line input I'm using looks like:

$ ./getfile.pl /path/to/some/file.csv

Here's what the beginning of the subroutine I'm calling looks like:

sub parse {
    my $handle = shift;
    my @data   = <$handle>;
    while (my $line = shift(@data)) {
      # do stuff
    }
}
A: 

Am I missing something or are you just looking for the open() call?

open($fh, "<$ARGV[0]") or die "couldn't open $ARGV[0]: $!";
do_something_with_fh($fh);
close($fh);
bmdhacks
This is wrong. It's open my $fh, "<$ARGV[0]". The $fh is the first argument, not the return value! (or better yet open my $fh, '<', $ARGV[0].
Leon Timmermans
fixed, thanks for the heads-up
bmdhacks
+16  A: 

Command line arguments are available in the predefined @ARGV array. You can get the file name from there and use open to open a filehandle to it. Assuming that you want read-only access to the file, you would do it this way:

my $file = shift @ARGV;
open(my $fh, '<', $file) or die "Can't read file '$file' [$!]\n";
parse($fh);

Note that the or die... checks the call open for success and dies with an error message if it wasn't. The built-in variable $! will contain the (OS dependent) error message on failure that tells you why the call wasn't successful. e.g. "Permission denied."

Michael Carman
You could also `use autodie;` to avoid having to check the `open` call for success manually. Currently you have to install autodie from the CPAN, but it’s probably going to be shipped with Perl as of 5.10.1.
Aristotle Pagaltzis
+2  A: 

parse(*ARGV) is the simplest solution: the explanation is a bit long, but an important part of learning how to use Perl effectively is to learn Perl.

When you use a null filehandle (<>), it actually reads from the magical ARGV filehandle, which has special semantics: it reads from all the files named in @ARGV, or STDIN if @ARGV is empty.

From perldoc perlop:

The null filehandle <> is special: it can be used to emulate the behavior of sed and awk. Input from <> comes either from standard input, or from each file listed on the command line. Here’s how it works: the first time <> is evaluated, the @ARGV array is checked, and if it is empty, $ARGV[0] is set to "-", which when opened gives you standard input. The @ARGV array is then processed as a list of filenames. The loop

while (<>) {
    ...                     # code for each line
}

is equivalent to the following Perl-like pseudo code:

unshift(@ARGV, '-') unless @ARGV;
while ($ARGV = shift) {
    open(ARGV, $ARGV);
    while (<ARGV>) {
        ...         # code for each line
    }
}

except that it isn’t so cumbersome to say, and will actually work. It really does shift the @ARGV array and put the current filename into the $ARGV variable. It also uses filehandle ARGV internally--<> is just a synonym for <ARGV>, which is magical. (The pseudo code above doesn’t work because it treats <ARGV> as non-magical.)

You don't have to use <> in a while loop -- my $data = <> will read one line from the first non-empty file, my @data = <>; will slurp it all up at once, and you can pass *ARGV around as if it were a normal filehandle.

ephemient
It doesn't quite answer the question directly... but suggesting, based on this, that the OP pass the ARGV filehandle to his function would. (Assuming, of course, that doing so would work - I haven't tried it.)
Dave Sherohman
Clarified this answer to say that parse(*ARGV) does work, instead of putting it in a comment on the question.
ephemient
A: 

This is what the -n switch is for!

Take your parse method, and do this:

#!/usr/bin/perl -n

#do stuff

Each line is stored in $_. So you run

./getfile.pl /path/to.csv

And it does this.

See here and here for some more info about these. I like -p too, and have found the combo of -a and -F to be really useful.

Also, if you want to do some extra processing, add BEGIN and end blocks.

#!/usr/bin/perl -n

BEGIN {
  my $accumulator;
}

# do stuff

END {
  print process_total($accumulator);
}

or whatever. This is very, very useful.

Steve Klabnik