13

I understand that I can get the current line number of a file I am looping through with the builtin variable $.. As an experiment, I used that to prefix each line in a file with the value of $. (the current line number). However, this did not work as expected. I.e., given the following file contents

line one
line two
line three

then I would expect the following code to prefix each line with its line number

for my $line (<FILE>) {
    print "$. : $line";
}

but, instead, it gives the following output

3 line one
3 line two
3 line three

prefixing each line with the number of lines in the file. Instead of the current line.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Friedrich 'Fred' Clausen
  • 3,321
  • 8
  • 39
  • 70

1 Answers1

21

That's because the way you wrote the loop reads the entire file before looping over the lines. Unless you have a special reason to need something better than simple sequential access to the file, you should use while instead of for, like this:

while (my $line = <FILE>) {
  print "$. : $line";
}

When < filehandle > is called in list context (as it is in your for loop), it returns the entire contents of the file as a list of lines. Therefore, your code behaves in much the same way as if you had written this instead:

my @lines = <FILE>;            # now $. is set to the end of the file 
for my $line (@lines) { ... }  # you're just looping over an array, not touching $.

To achieve your desired result, you should call <> repeatedly in scalar context (which the assignment in the while condition does), to fetch one line at a time from the file and execute the body of the loop with $. set to the correct number.

Also, global filehandles are considered to be bad practice. For several reasons, it's better to use a filehandle referenced by a lexical variable instead, like this:

open my $file, '<', $filename or die $!;
while (my $line = <$file>) {
  print "$. : $line";
}

Also also, since $. is a global variable containing the line number from the most recently executed read operation, you shouldn't rely on it if there's any chance of another read occurring between the <$file> and the print. Instead, ask the filehandle you're using for its line number:

open my $file, '<', $filename or die $!;
while (my $line = <$file>) {
  print $file->input_line_number, " : $line";
}

Which even works, if somewhat more awkwardly, with a global filehandle:

while (my $line = <FILE>) {
  print ${\*FILE}->input_line_number, " : $line";
}

... even the default one read by an empty <>, which is really named ARGV:

while (my $line = <>) {
  print ${\*ARGV}->input_line_number, " : $line";
}
Mark Reed
  • 91,912
  • 16
  • 138
  • 175
  • Thanks for the clarification and note about bare file handles - I think I will pick up a copy of [Modern Perl](http://www.onyxneon.com/books/modern_perl/index.html) to brush up on the state of the art. – Friedrich 'Fred' Clausen Nov 16 '13 at 16:38
  • 4
    Good answer. Note that the check for `defined` is unnecessary in that context -- Perl does it automatically. Can't find the spot in the documentation, but this example shows it: `perl -MO=Deparse -e "while (my $line = <$fh>){print}"`. – FMc Nov 16 '13 at 16:44
  • @FMc The read returns `undef` at the end which evaluates to false in the conditional. See [Truth and Falsehood in Perlsyn](http://perldoc.perl.org/perlsyn.html#Truth-and-Falsehood) – user2676699 Nov 16 '13 at 17:43
  • 2
    @user2676699 I think that's not quite the point. Rather, the point is that Perl inserts a call to `defined()` for you. See the output from the code I posted, and this: http://perldoc.perl.org/perlop.html#I%2fO-Operators. – FMc Nov 16 '13 at 18:38
  • Thanks, @FMc. I missed that particular improvement. – Mark Reed Nov 16 '13 at 19:09
  • 3
    There's also the risk that `$.` will change between the time you `<$fh>` and `print`, if any intermediate code does its own `<$fh>` code, because `$.` always refers to the line number of the most recently read filehandle, and is not lexical. If you want to avoid that, use `IO::Handles` `$fh->input_line_number` – Kent Fredric Nov 16 '13 at 23:15
  • @user2676699 the risk is that a valid line read from the file might also evaluate to Perlishly falsy - for instance, it might be the string "0\n". Without the automatically-inserted `defined` check, that would terminate the loop prematurely. (Plain `while ` has had definedness magic for a long time, but the fact that it works even when there's an assignment is *relatively* new, and was even newer when I wrote this 3 years ago. :)) – Mark Reed Oct 11 '16 at 17:49