Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

SIMPLE file reading in Perl

Tags:

file

perl

How does Perl read in files, how does it tell it to advance to the next line in the text file, and how does it make it read all lines in the .txt file until, for example, it reaches item "banana"?

like image 628
Befall Avatar asked May 11 '10 11:05

Befall


2 Answers

Basically, there are two ways of reading files:

  1. Slurping a file means reading the file all at once. This uses a lot of memory and takes a while, but afterwards the whole file contents are in memory and you can do what you want with it.
  2. Reading a file line-per-line (in a while loop) is better if you don't want to read the entire file (for example, stop when you reach "banana").

For both ways you need to create a FILEHANDLE using the "open" command, like so:

open(my $yourhandle, '<', 'path/to/file.txt') # always use a variable here containing filename
    or die "Unable to open file, $!";

Then you can either slurp the file by putting it into an array:

my @entire_file=<$yourhandle>; # Slurp!

or read the file one by one using a while loop

while (<$yourhandle>) { # Read the file line per line (or otherwise, it's configurable).
   print "The line is now in the $_ variable";
   last if $_ eq 'banana'; # Leave the while-loop.
}

Afterwards, don't forget to close the file.

close($yourhandle)
    or warn "Unable to close the file handle: $!";

That's just the basics.. there's a lot to do with files, especially in Exception handling (what to do when the file does not exist, is not readable, is being written to), so you'll have to read up or ask away :)

like image 53
Konerak Avatar answered Oct 04 '22 09:10

Konerak


René and Konerak wrote a couple of pretty good responses that show how to open and read a file. Unfortunately they have some issues in terms of promoting best practices. So, I'll come late to the party and try to add clear explanation of best practices approach and why it is better to use the best practice approach.

What is a file handle?

A file handle is a name we use that represents the file itself. When you want to operate on a file (read it, write to it, move around, etc.) use the file handle to indicate which file to operate on. A file handle is distinct from the file's name or path.

Variable scope and file handles

A variable's scope determines in what parts of a program the variable can be seen. In general, it is a good idea to keep the scope on every variable as small possible so that different parts of a complex program don't break each other.

The easiest way to strictly control a variable's scope in Perl is to make it a lexical variable. Lexical variables are only visible inside the block in which they are declared. Use my to declare a lexical variable: my $foo;

# Can't see $foo here

{   my $foo = 7;
    print $foo;
}

# Can't see $foo here

Perl file handles can be global or lexical. When you use open with a bare word (a literal string without quotes or a sigil), you create a global handle. When you open on an undefined lexical scalar, you create a lexical handle.

open FOO, $file;      # Global file handle
open my $foo, $file;  # Lexical file handle

# Another way to get a lexical handle:
my $foo;
open $foo, $file;

The big problem with global file handles is that they are visible anywhere in the program. So if I create a file handle named FOO in subroutine, I have to very careful to ensure that I don't use the same name in another routine, or if I use the same name, I must be absolutely certain that under no circumstances can they conflict with each other. The simple alternative is to use a lexical handle that cannot have the same kind of name conflicts.

Another benefit of lexical handles is that it is easy to pass them around as subroutine arguments.

The open function

The open function has all kinds of features. It can run subprocesses, read files, and even provide a handle for the contents of a scalar. You can feed it many different types of argument lists. It is very powerful and flexible, but these features come with some gotchas (executing subprocesses is not something you want to do by accident).

For the simple case of opening a file, it is best to always use the 3-argument form because it prevents unintended activation of all those special features:

open FILEHANDLE, MODE, FILEPATH

FILEHANDLE is the file handle to open.

MODE is how to open the file, > for overwrite, '>>for write in append mode,+>for read and write, and<` for read.

FILEPATH is the path to the file to open.

On success, open returns a true value. On failure, $! is set to indicate the error, and a false value is returned.

So, to make a lexical file handle with a 3-argument open that we can use to read a file:

open my $fh, '<', $file_path;

The logical return values make it easy to check for errors:

open my $fh, '<', $file_path
    or die "Error opening $file_path - $!\n";

I like to bring the error handling down to a new line and indent it, but that's personal style.

Closing handles

When you use global handles it is critical to carefully, explicitly close each and every handle when you are done with it. Failure to do so can lead to odd bugs and maintainability problems.

close FOO;

Lexical handles automatically close when the variable is destroyed (when the reference count drops to 0, usually when the variable goes out of scope).

When using lexical handles it is common to rely on the implicit closure of handles rather than explicitly closing them.

Diamonds are a Perl's best friend.

The diamond operator, <>, allows us to iterate over a file handle. Like open it has superpowers. We'll ignore most of them for now. (Search for info on the input record separator, output record separator and the NULL file handle to learn about them, though.)

The important thing is that in scalar context (e.g. assigning to a scalar) it acts like a readline function. In list context (e.g. assigning to an array) it acts like a read_all_lines function.

Imagine you want to read a data file with three header lines (date, time and location) and a bunch of data lines:

open my $fh, '<', $file_path
    or die "Ugh - $!\n";

my $date = <$fh>;
my $time = <$fh>;
my $loc  = <$fh>;

my @data = <$fh>;

It's common in to hear people talk about slurping a file. This means to read the whole file into a variable at once.

 # Slurp into array
 my @slurp = <$fh>;

 # Slurp into a scalar - uses tricks outside the scope of this answer
 my $slurp;
 { local $/ = undef; $slurp = <$fh>; }

Putting it all together

open my $fh, '<', 'my_file'
    or die "Error opening file - $!\n";

my @before_banana;

while( my $line = <$fh> ) {
    last if $line =~ /^banana$/;

    push @before_banana, $line;
}

Putting it all together - special extra credit edition

my $fh = get_handle( 'my_file' );

my @banana = read_until( $fh, qr/^banana$/ );  # Get the lines before banana

read_until( $fh, qr/^no banana$/ );            # Skip some lines

my @potato = read_until( $fh, qr/^potato$/ );  # Get the lines before potato

sub get_handle {
    my $file_path = shift;

    open my $fh, '<', $file_path
        or die "Can't open '$file_path' for reading - $!\n";

    return $fh;
}

sub read_until {
    my $fh    = shift;
    my $match = shift;

    my @lines;

    while( my $line = <$fh> ) {
        last if $line =~ /$match/;
        push @line, $line;
    }

    return @lines;
}

Why so many different ways? Why so many gotchas?

Perl is an old language; it has baggage dating all the way back to 1987. Over the years various design issues were found and fixes were made--but only rarely were fixes allowed to harm backwards compatibility.

Further, Perl is designed to give you the flexibility to do what you want to, when you want to. It is very permissive. The good thing about this is that you can reach down into the murky depths and do really cool magical stuff. The bad thing is that it is easy to shoot yourself in the foot if you forget to temper your exuberance and fail to focus on producing readable code.

Just because you've got more than enough rope, doesn't mean that you have to hang yourself.

like image 25
daotoad Avatar answered Oct 04 '22 08:10

daotoad