Perl Part I · Module 15 · Command-Line Perl and Script Execution
@ARGV: Reading Command-Line Arguments
A script with the filename typed into line 12 is a script that can only ever do one job. Taking the filename from the command line instead is the single change that turns a throwaway into a tool other people can use, and in Perl it costs one array. @ARGV holds what was typed after the script name, already split into separate words for you. The part worth real attention is what happens before your script starts: the shell has already expanded your patterns, removed your quotes and substituted your variables, so what Perl receives is not what you typed. This chapter measures that difference, because almost every confusing argument bug comes from it.
Foundation12 min readPerlPart ICommand Line
Part I · Module 15 · Chapter 15.1 · @ARGV: Reading Command-Line Arguments
1. What @ARGV Holds
@ARGV is an ordinary array that Perl fills in before your first line runs. It contains the arguments, already split into words.
use strict;
use warnings;
use File::Temp qw(tempdir);
use File::Spec;
my $dir = tempdir(CLEANUP => 1);
my $show = File::Spec->catfile($dir, "show.pl");
open my $w, ">", $show or die "cannot write: $!\n";
print $w <<'PROG';
print "\$0 = $0\n";
print "scalar \@ARGV = ", scalar(@ARGV), "\n";
for my $i (0 .. $#ARGV) {
print " \$ARGV[$i] = [$ARGV[$i]]\n";
}
PROG
close $w;
# run it with three arguments, the third containing a space
my $out = `cd '$dir' && perl show.pl one two "three four"`;
print $out;$0 = show.pl
scalar @ARGV = 3
$ARGV[0] = [one]
$ARGV[1] = [two]
$ARGV[2] = [three four]Three things are measured there:
$0 is the script's name, and it is not in @ARGV. This differs from C, where argv[0] is the program name, and from sys.argv in Python. In Perl the two are separate: the name is in $0, the arguments are in @ARGV. So $ARGV[0] is the first argument, not the program.
scalar(@ARGV) is the number of arguments, and @ARGV in boolean context is false when there are none. That makes if (!@ARGV) { ... } the natural way to detect "no arguments given".
"three four" arrived as one argument. The quotes grouped it. They are not part of the value — the brackets in the output show the value is three four with no quote characters in it.
2. The Shell Gets There First
This is the part that causes real confusion, and it is best measured rather than described.
use strict;
use warnings;
use File::Temp qw(tempdir);
use File::Spec;
my $dir = tempdir(CLEANUP => 1);
my $show = File::Spec->catfile($dir, "show.pl");
open my $w, ">", $show or die "cannot write: $!\n";
print $w 'printf "%d arg(s): %s\n", scalar(@ARGV), join(" | ", map { "[$_]" } @ARGV);' . "\n";
close $w;
# two files for the pattern to match
for my $f (qw(x.log y.log)) {
open my $t, ">", File::Spec->catfile($dir, $f) or die "cannot write: $!\n";
close $t;
}
for my $cmd ('perl show.pl *.log',
"perl show.pl '*.log'",
'perl show.pl a b c',
'perl show.pl "a b c"') {
my $out = `cd '$dir' && $cmd 2>&1`;
$out =~ s/\s+\z//;
printf "%-28s -> %s\n", $cmd, $out;
}perl show.pl *.log -> 2 arg(s): [x.log] | [y.log]
perl show.pl '*.log' -> 1 arg(s): [*.log]
perl show.pl a b c -> 3 arg(s): [a] | [b] | [c]
perl show.pl "a b c" -> 1 arg(s): [a b c]Read the first two rows together, because they are the same script given the same characters:
*.logunquoted became two arguments,x.logandy.log. The shell found the files, replaced the pattern with their names, and Perl was started with the result. Perl never saw the asterisk.'*.log'quoted arrived as one argument whose value is literally*.log. Now Perl sees the asterisk and could expand it itself withglob, if that is what you wanted.
The same mechanism explains the other rows, and three more things you will hit:
Variables are substituted by the shell. perl show.pl $HOME gives Perl the expanded path. Perl has no idea a variable was involved.
Your script cannot "get the original command line". By the time it runs, the original text is gone. $0 plus @ARGV is a reconstruction, not a recording, and it will differ from what was typed.
A filename with a space must be quoted or escaped, or the shell splits it into two arguments. This is the most common real bug of the three: a path like /proj/Rev A/setup.rpt silently becomes two arguments, and the script reports that /proj/Rev does not exist.
3. shift, and the Diamond Operator
Two idioms do most of the work of reading @ARGV.
use strict;
use warnings;
use File::Temp qw(tempdir);
use File::Spec;
my $dir = tempdir(CLEANUP => 1);
# --- shift, outside any subroutine, defaults to @ARGV
my $s = File::Spec->catfile($dir, "sh.pl");
open my $w, ">", $s or die "cannot write: $!\n";
print $w <<'PROG';
print "before : @ARGV\n";
my $first = shift; # in main: shift @ARGV
print "shift -> '$first'\n";
print "after : @ARGV\n";
PROG
close $w;
print `cd '$dir' && perl sh.pl alpha beta gamma`;
# --- the diamond operator reads every file named in @ARGV
my $d = File::Spec->catfile($dir, "dia.pl");
open my $w2, ">", $d or die "cannot write: $!\n";
print $w2 <<'PROG';
print "ARGV before <> : @ARGV\n";
my $n = 0;
while (<>) { $n++ }
print "lines read : $n\n";
print "ARGV after <> : ", (@ARGV ? "@ARGV" : "(empty)"), "\n";
print "\$ARGV : $ARGV\n";
PROG
close $w2;
for my $pair (["f1.txt", "a\nb\n"], ["f2.txt", "c\n"]) {
open my $t, ">", File::Spec->catfile($dir, $pair->[0]) or die "cannot write: $!\n";
print $t $pair->[1];
close $t;
}
print "\n", `cd '$dir' && perl dia.pl f1.txt f2.txt`;before : alpha beta gamma
shift -> 'alpha'
after : beta gamma
ARGV before <> : f1.txt f2.txt
lines read : 3
ARGV after <> : (empty)
$ARGV : f2.txtshift with no argument, outside a subroutine, shifts @ARGV. Inside a subroutine it shifts @_ instead — that dual behaviour is Chapter 10.1's, and it is why my $self = shift; works in a method.
So the usual opening of a small tool is a sequence of shifts:
my $report = shift or die "usage: $0 REPORT [LIMIT]\n";
my $limit = shift // 10;Note or die on the first and // on the second. The first is required, so an empty or missing value is fatal. The second is optional with a default, and // is correct rather than || because a deliberate 0 must survive — Chapter 3.8's distinction.
The diamond operator <> reads every file named in @ARGV, in order, as if they were one stream. Measured: three lines came back from two files. And then:
<> empties @ARGV as it goes. After the loop, @ARGV is empty — the files have been consumed. If you need the list afterwards, copy it first. $ARGV (no sigil change, singular) holds the file currently being read, which is how you report which file a bad line came from.
With @ARGV empty, <> reads standard input instead. That single behaviour is what makes a Perl script act like a normal Unix filter, and Chapter 15.12 builds on it.
4. Arguments Are Always Strings
Everything in @ARGV arrives as a string. Perl converts when you use it as a number, which is usually what you want and occasionally not.
use strict;
use warnings;
use File::Temp qw(tempdir);
use File::Spec;
my $dir = tempdir(CLEANUP => 1);
my $p = File::Spec->catfile($dir, "t.pl");
open my $w, ">", $p or die "cannot write: $!\n";
print $w <<'PROG';
my ($v) = @ARGV;
no warnings 'numeric';
printf "%-8s +0 = %-6s eq '007'? %-3s == 7? %-3s length %d\n",
"[$v]", $v + 0, ($v eq '007' ? "yes" : "no"), ($v == 7 ? "yes" : "no"), length $v;
PROG
close $w;
for my $arg (qw(007 7 7.0 3.5ns abc)) {
print `cd '$dir' && perl t.pl $arg`;
}[007] +0 = 7 eq '007'? yes == 7? yes length 3
[7] +0 = 7 eq '007'? no == 7? yes length 1
[7.0] +0 = 7 eq '007'? no == 7? yes length 3
[3.5ns] +0 = 3.5 eq '007'? no == 7? no length 5
[abc] +0 = 0 eq '007'? no == 7? no length 3The 007 row is the one to look at. It is eq '007' and == 7 at the same time, because the two operators ask different questions. Use eq when the exact text matters — a revision label, a cell name, a version string — and == when the value matters.
The last two rows repeat Chapter 14.7's measured warning in a new place: 3.5ns numifies to 3.5 with the unit discarded, and abc numifies to 0. An argument that is supposed to be a number should be checked, not assumed:
my $limit = shift // 10;
die "limit must be a whole number, got '$limit'\n"
unless $limit =~ /\A\d+\z/;That is \A and \z from Chapter 11.8 again: the whole argument must be digits, so 10ns and 10 are both rejected rather than quietly becoming 10.
5. What To Remember
@ARGVholds the arguments;$0holds the script's name. They are separate, unlike C'sargv[0].$ARGV[0]is the first argument, not the program name.if (!@ARGV)is how you detect that nothing was given.- It is an ordinary array —
shift,grep, slices and assignment all work. - The shell expands before Perl starts. Measured:
*.logarrived as two arguments and Perl never saw the asterisk;'*.log'arrived as one literal argument. - Quotes group, and are removed.
"three four"is one argument whose value contains a space but no quotes. - A path with a space must be quoted, or it silently becomes two arguments.
- You cannot recover the original command line —
$0and@ARGVare a reconstruction. shiftin main shifts@ARGV; inside a subroutine it shifts@_.shift or diefor a required argument,shift // defaultfor an optional one —//so a real0survives.<>reads every file in@ARGVas one stream, and empties@ARGVdoing it. Measured: 3 lines from 2 files, then@ARGVempty.$ARGVnames the file being read now, which is what you quote in an error message.- With
@ARGVempty,<>reads standard input, which is what makes a Perl script a Unix filter. - Every argument is a string.
007is botheq '007'and== 7. - Check an argument that must be a number with
\A\d+\z, or3.5nsbecomes3.5andabcbecomes0.
Chapter 15.2 moves past positional arguments. Once a tool has more than two or three inputs, remembering which one goes third stops working, and Getopt::Long replaces the position with a name.
Where this fits
Part of the Perl curriculum.
