Skip to content
VLSI Mentor

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.

Azvya Education Pvt. Ltd.VLSI Mentor
what-argv-holds.pl
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;
Azvya Education Pvt. Ltd.VLSI Mentor
Captured output
$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.

Azvya Education Pvt. Ltd.VLSI Mentor
shell-first.pl
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;
}
Azvya Education Pvt. Ltd.VLSI Mentor
Captured output
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:

  • *.log unquoted became two arguments, x.log and y.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 with glob, 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.

Azvya Education Pvt. Ltd.VLSI Mentor
shift-and-diamond.pl
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`;
Azvya Education Pvt. Ltd.VLSI Mentor
Captured output
before : alpha beta gamma
shift   -> 'alpha'
after  : beta gamma

ARGV before <> : f1.txt f2.txt
lines read     : 3
ARGV after  <> : (empty)
$ARGV          : f2.txt

shift 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:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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.

Azvya Education Pvt. Ltd.VLSI Mentor
arguments-are-strings.pl
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`;
}
Azvya Education Pvt. Ltd.VLSI Mentor
Captured output
[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 3

The 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:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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

  • @ARGV holds the arguments; $0 holds the script's name. They are separate, unlike C's argv[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: *.log arrived 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 — $0 and @ARGV are a reconstruction.
  • shift in main shifts @ARGV; inside a subroutine it shifts @_.
  • shift or die for a required argument, shift // default for an optional one — // so a real 0 survives.
  • <> reads every file in @ARGV as one stream, and empties @ARGV doing it. Measured: 3 lines from 2 files, then @ARGV empty.
  • $ARGV names the file being read now, which is what you quote in an error message.
  • With @ARGV empty, <> reads standard input, which is what makes a Perl script a Unix filter.
  • Every argument is a string. 007 is both eq '007' and == 7.
  • Check an argument that must be a number with \A\d+\z, or 3.5ns becomes 3.5 and abc becomes 0.

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.