Current file line number with $. variable

2019-06-16 15:25发布

问题:

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.

回答1:

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, 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";
}