Perl Part I · Module 13 · File Handling
The Diamond Operator and @ARGV
Every command-line tool you use behaves the same way: give it filenames and it reads those files, give it nothing and it reads whatever you pipe into it. Perl gives you that behaviour for free, in four characters. Writing angle brackets with nothing inside them reads the files named on the command line in order, or standard input if there were none, and it opens and closes each file for you. That one change is what makes a script usable inside a pipeline instead of only on the paths you hard-coded. The chapter also covers the thing about it that is easy to get wrong, and which caught me while writing this page: the operator does not just read the list of filenames, it consumes it, so the obvious way to ask how your script was called gives the wrong answer if you ask too late.
Intermediate10 min readPerlPart IFile Handling
Part I · Module 13 · Chapter 13.7 · The Diamond Operator and @ARGV
1. Reading the Files Named on the Command Line
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 "file$n line A\nfile$n line B\n";
close $w;
}
# A child script that uses the diamond operator.
my $child = File::Spec->catfile($dir, "cat.pl");
open my $c, ">", $child or die $!;
print $c <<'SCRIPT';
use strict;
use warnings;
while (my $line = <>) {
chomp $line;
printf "%-12s line %d : %s\n", $ARGV, $., $line;
}
SCRIPT
close $c;
# Run it with TWO filenames as arguments.
my $out = `cd "$dir" && perl cat.pl f1.txt f2.txt 2>&1`;
print $out;f1.txt line 1 : file1 line A
f1.txt line 2 : file1 line B
f2.txt line 3 : file2 line A
f2.txt line 4 : file2 line BThe whole read loop in that child script is three lines, and it handled two files:
while (my $line = <>) { ... }
Compare it with the loop from Chapter 13.2. There is no open, no filename, no close, and no loop over a list of files. <> did all of that: it took the names from the command line, opened each in turn, read every line, and closed each one.
Two special variables come with it, and both appear in the output:
$ARGVis the name of the file currently being read. That is how each line in the output knows which file it came from — essential when your script reports a problem and the user needs to know where.$.is the line number, and look carefully: the second file's lines are numbered 3 and 4, not 1 and 2.<>keeps one continuous count across all the files. Chapter 13.8 measures that against the alternative and shows how to reset it.
This is the shape to reach for whenever a script processes files a user names. It costs less code than the explicit version and it gives your script the behaviour people already expect.
2. No Arguments Means Standard Input
use strict;
use warnings;
use File::Temp qw(tempdir);
use File::Spec;
my $dir = tempdir(CLEANUP => 1);
my $child = File::Spec->catfile($dir, "count.pl");
open my $c, ">", $child or die $!;
print $c <<'SCRIPT';
use strict;
use warnings;
# Record @ARGV BEFORE reading: <> SHIFTS names off @ARGV as it goes.
my $named = scalar @ARGV;
my $n = 0;
$n++ while <>;
printf "argv at start: %d lines read: %d left in argv: %d\n",
$named, $n, scalar @ARGV;
SCRIPT
close $c;
open my $w, ">", "$dir/a.txt" or die $!; print $w "1\n2\n3\n"; close $w;
printf "with a filename : %s", `cd "$dir" && perl count.pl a.txt 2>&1`;
printf "no argument : %s", `cd "$dir" && printf 'x\ny\n' | perl count.pl 2>&1`;
print "\n<> empties \@ARGV as it reads, so test it BEFORE the loop\n";with a filename : argv at start: 1 lines read: 3 left in argv: 0
no argument : argv at start: 0 lines read: 2 left in argv: 0
<> empties @ARGV as it reads, so test it BEFORE the loopTwo runs of the same script. Given a filename it read that file's 3 lines. Given no arguments and a pipe, it read the 2 lines from standard input.
That fallback is the second half of what makes <> worth using. The same script works in all of these:
perl report.pl setup.rpt hold.rpt
perl report.pl *.rpt
grep ERROR run.log | perl report.pl
perl report.pl < one.rptYou wrote one loop. No branch for "did they give me files", no special case for the pipe. And because - is understood as standard input, even perl report.pl a.rpt - b.rpt works.
3. The Diamond Operator Consumes @ARGV
Now the advanced point. It is the reason the script above records scalar @ARGV on its first line rather than at the end.
Read the two numbers at each end of the output: argv at start: 1 and left in argv: 0.
<> works by shifting names off @ARGV one at a time. When it needs a new file it removes the next name from the front of the array and opens it. So by the time the loop finishes, @ARGV is empty — in both runs.
Three practical consequences follow from the same mechanism:
Options must be removed before the loop. If a user writes perl report.pl --verbose setup.rpt, then --verbose is in @ARGV and <> will try to open a file called --verbose. The fix is to parse options first: Getopt::Long from Chapter 12.9 removes the switches it recognises and leaves the filenames, which is exactly the order <> needs.
You can set @ARGV yourself. It is an ordinary array. @ARGV = @files; while (<>) { ... } makes the diamond operator read a list you built, which is useful when the files come from a directory scan rather than the command line.
A missing file is a warning, not a stop. <> complains to stderr about a file it cannot open and then moves on to the next one. That is forgiving for a user and dangerous for a script that is counting things, because the total silently excludes a file. If every named file must be readable, check them before the loop: for (@ARGV) { -r or die "cannot read $_\n" }.
4. What To Remember
<>reads every file named on the command line, opening and closing each one for you. Three lines replace the whole loop.- With no arguments it reads standard input, so the same script works with filenames, a glob, or a pipe.
$ARGVis the current filename — use it in messages so a user knows where a problem is.$.counts continuously across all the files, not per file. Chapter 13.8 shows how to reset it.<>empties@ARGVas it reads. Measured: 1 name at the start, 0 left at the end.- So capture
scalar @ARGVbefore the loop if you need to know how you were called. Measured: asking afterwards reported STDIN for a run that read a named file. - Parse options first.
Getopt::Longleaves exactly the filenames behind, which is what<>wants. @ARGVis a normal array — assign to it to feed<>a list you built.- An unreadable file is a warning and the loop continues, so check readability first if your counts must be complete.
<<>>is the safe variant that always treats arguments as filenames.
Chapter 13.8 takes the line-number variable this chapter introduced and measures the one thing about it that surprises people: whether it restarts when you move to a new file, and what to write when you need it to.
Where this fits
Part of the Perl curriculum.
