Skip to content
VLSI Mentor

Perl Part I · Module 13 · File Handling

Line Numbers With $. in a Read Loop

When a script finds a malformed line in a report, there are two ways it can tell you. It can say the line was malformed, which leaves you to go and find it. Or it can say which line number it was, which means you open the file at that line and see the problem immediately. The difference is one variable, and it costs nothing because Perl is already counting. This chapter covers what it holds and the one thing about it that catches people out — whether the count restarts when your script moves on to the next file. Section two answers that by measuring both cases side by side, because the answer is different depending on how you opened the files.

Foundation9 min readPerlPart IFile Handling

Part I · Module 13 · Chapter 13.8 · Line Numbers With $. in a Read Loop

1. The Variable

Azvya Education Pvt. Ltd.VLSI Mentor
line-numbers.pl
use strict;
use warnings;
use File::Temp qw(tempdir);

my $dir = tempdir(CLEANUP => 1);
open my $w, ">", "$dir/r.txt" or die $!;
print $w "alpha\nbeta\ngamma\n"; close $w;

open my $fh, "<", "$dir/r.txt" or die $!;
while (my $line = <$fh>) {
    chomp $line;
    printf "line %d : %s\n", $., $line;
}
printf "after the loop, \$. is %d (the last line number)\n", $.;
close $fh;
Azvya Education Pvt. Ltd.VLSI Mentor
Captured output
line 1 : alpha
line 2 : beta
line 3 : gamma
after the loop, $. is 3 (the last line number)

$. is the line number of the line you have just read. You do not have to declare it, set it, or increment it — Perl keeps it updated as a side effect of reading.

That makes a useful error message almost free:

warn "line $.: cannot parse '$line'\n";

Compare that with the version without it. "Cannot parse a line" tells a user that something is wrong. "line 4817: cannot parse ..." tells them where to look. On a report with fifty thousand lines that is the difference between a one-minute fix and a grep session.

Note the last line of output: after the loop, $. still holds 3. It is not reset when the loop ends, so you can use it afterwards to report how many lines you processed — though my $count = 0; $count++ inside the loop is clearer if that is what you mean.

2. Does It Restart for the Next File?

This is the question worth measuring, because the answer depends on how you read the files.

Azvya Education Pvt. Ltd.VLSI Mentor
per-handle-counter.pl
use strict;
use warnings;
use File::Temp qw(tempdir);
use File::Spec;

my $dir = tempdir(CLEANUP => 1);
for my $n (1 .. 2) {
    open my $w, ">", "$dir/f$n.txt" or die $!;
    print $w "a\nb\n"; close $w;
}

# TWO SEPARATE HANDLES: each one counts its own lines from 1.
print "two separate handles:\n";
for my $n (1 .. 2) {
    open my $fh, "<", "$dir/f$n.txt" or die $!;
    while (<$fh>) { printf "  f%d.txt line %d\n", $n, $. }
    close $fh;
}

# THE DIAMOND OPERATOR over the same two files: ONE continuous count.
my $child = File::Spec->catfile($dir, "d.pl");
open my $c, ">", $child or die $!;
print $c qq{while (<>) { printf "  %-8s line %d\\n", \$ARGV, \$. }\n};
close $c;
print "\nthe diamond operator over both files:\n";
print `cd "$dir" && perl d.pl f1.txt f2.txt 2>&1`;
print "  <- the count did NOT restart at the second file\n";
Azvya Education Pvt. Ltd.VLSI Mentor
Captured output
two separate handles:
  f1.txt line 1
  f1.txt line 2
  f2.txt line 1
  f2.txt line 2

the diamond operator over both files:
  f1.txt   line 1
  f1.txt   line 2
  f2.txt   line 3
  f2.txt   line 4
  <- the count did NOT restart at the second file

Same two files, same two lines each, two different answers.

With separate handles, each file counted 1, 2. That is because $. belongs to a filehandle, not to the program — it is the line number of the last read on whichever handle you read from most recently, and a fresh open starts a fresh count.

With the diamond operator, the second file's lines were 3 and 4. <> treats all the named files as one continuous stream, so the count runs straight through.

Both behaviours are defensible and which one you want depends on the message you are writing:

  • If you report "$ARGV line $.", you want per-file numbering, so the user can open that file at that line.
  • If you are reporting a position in a concatenated stream — the way cat a b | tool behaves — the continuous count is correct.

The first is almost always what a user wants. Section three is how to get it from <>.

3. Resetting the Count

Azvya Education Pvt. Ltd.VLSI Mentor
reset-with-close.pl
use strict;
use warnings;
use File::Temp qw(tempdir);
use File::Spec;

my $dir = tempdir(CLEANUP => 1);
for my $n (1 .. 2) {
    open my $w, ">", "$dir/f$n.txt" or die $!;
    print $w "a\nb\n"; close $w;
}

# close ARGV at the end of each file makes <> restart the count.
my $child = File::Spec->catfile($dir, "d.pl");
open my $c, ">", $child or die $!;
print $c <<'SCRIPT';
while (<>) {
    printf "  %-8s line %d\n", $ARGV, $.;
    close ARGV if eof;          # eof (no parens) = end of the CURRENT file
}
SCRIPT
close $c;
print "with 'close ARGV if eof':\n";
print `cd "$dir" && perl d.pl f1.txt f2.txt 2>&1`;
print "  <- now each file counts from 1\n";
Azvya Education Pvt. Ltd.VLSI Mentor
Captured output
with 'close ARGV if eof':
  f1.txt   line 1
  f1.txt   line 2
  f2.txt   line 1
  f2.txt   line 2
  <- now each file counts from 1

One extra line in the loop, and the second file counts from 1 again.

close ARGV if eof; is the standard idiom and it deserves unpacking, because the detail that makes it work is easy to miss.

ARGV is the filehandle <> is using internally. Closing it resets that handle's line counter, and <> simply opens the next file when it needs one — so closing it does not end the loop.

eof without parentheses means "the end of the file currently being read". eof() with empty parentheses means "the end of the whole <> stream, all files included". They are different functions and the idiom needs the first one. Writing close ARGV if eof() would only fire once, at the very end, and reset nothing useful.

Two related variables worth knowing while you are here, both covered elsewhere:

  • $ARGV is the current filename under <>, from Chapter 13.7. "$ARGV line $." is the message you want.
  • $/ is the input record separator, from Chapter 12.8. If you change it to read records rather than lines, $. counts records, not lines — which is correct and occasionally surprising.

And one note on $_. The loops in sections two and three are written while (<$fh>) with no variable, which puts each line in $_. That is idiomatic and fine in a short loop, and Chapter 11.3 measured the trap: in anything longer, name the variable, because a bare $_ is easy to clobber by calling something that also uses it.

4. What To Remember

  • $. is the line number of the last read, maintained for you. No declaration, no counter.
  • Put it in your messages. "line $.: ..." is the difference between a user grepping and a user looking.
  • It survives the loop, so it holds the final line number afterwards.
  • $. belongs to a filehandle. Measured: separate handles counted 1,2 then 1,2.
  • The diamond operator counts continuously. Measured: 1,2 then 3,4 across two files.
  • close ARGV if eof; resets it per file under <>, and does not end the loop.
  • eof and eof() are different. No parentheses means the current file; empty parentheses means the whole stream. The idiom needs the first.
  • "$ARGV line $." is the message format worth standardising on.
  • If you change $/, $. counts records, not lines.
  • while (<$fh>) uses $_ — fine in a short loop, risky in a long one.

Chapter 13.9 moves from files to the directories that hold them: listing a directory with opendir and readdir, the two entries every directory contains that you almost never want, and creating directories safely.

Where this fits

Part of the Perl curriculum.