Skip to content
VLSI Mentor

Perl Part I · Module 15 · Command-Line Perl and Script Execution

Getopt::Long: Real Option Parsing

Positional arguments work for two inputs and stop working at four. Nobody remembers whether the threshold goes before or after the output directory, and a tool that silently does the wrong thing when they are swapped is worse than one that refuses. Named options fix it, and Perl ships the parser: Getopt::Long is a core module that handles values, on/off switches, repeated options and the conventions users already expect. Setting it up takes six lines. What this chapter spends its length on is the behaviour you get without asking for it — the module is deliberately permissive, it accepts abbreviations and the wrong case by default, and it reports a bad option by returning false rather than by stopping. All three are measured here, because all three have surprised people in production.

Intermediate13 min readPerlPart ICommand Line

Part I · Module 15 · Chapter 15.2 · Getopt::Long: Real Option Parsing

1. Why Positions Stop Working

A two-argument tool is fine positionally:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
report-slack.pl setup.rpt 0.1

Add three more inputs and it becomes unreadable:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
report-slack.pl setup.rpt 0.1 /tmp/out 1 0 hold

Nobody can read that, and nothing catches a swap — the script happily uses 1 as a directory and /tmp/out as a threshold. The same call with names is self-describing, and the order stops mattering:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
report-slack.pl --input setup.rpt --threshold 0.1 --outdir /tmp/out --verbose --check hold

Getopt::Long is in core, so there is nothing to install. Chapter 12.9 listed it among the modules you can rely on anywhere.

2. The Four Shapes You Need

Almost every option is one of four kinds. This one script shows all of them.

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

my $dir = tempdir(CLEANUP => 1);
my $tool = File::Spec->catfile($dir, "tool.pl");

open my $w, ">", $tool or die "cannot write: $!\n";
print $w <<'PROG';
use strict;
use warnings;
use Getopt::Long;

my $file    = "";        # --file NAME       a string value
my $count   = 0;         # --count N         an integer value
my $verbose = 0;         # --verbose/--noverbose   an on/off switch
my @tags;                # --tag X --tag Y    may be repeated

my $ok = GetOptions(
    "file=s"   => \$file,
    "count=i"  => \$count,
    "verbose!" => \$verbose,
    "tag=s"    => \@tags,
);

printf "ok=%d file=[%s] count=%d verbose=%d tags=[%s] leftover=[%s]\n",
    ($ok ? 1 : 0), $file, $count, $verbose,
    join("|", @tags), join("|", @ARGV);
PROG
close $w;

for my $args ('--file report.rpt --count 5 --verbose --tag a --tag b extra1 extra2',
              '--file=r.rpt --noverbose leftover',
              '--tag one --tag two --tag three',
              '') {
    my $out = `cd '$dir' && perl tool.pl $args 2>&1`;
    $out =~ s/\s+\z//;
    printf "%s\n   -> %s\n", ($args eq "" ? "(no arguments at all)" : $args), $out;
}
Azvya Education Pvt. Ltd.VLSI Mentor
Captured output
--file report.rpt --count 5 --verbose --tag a --tag b extra1 extra2
   -> ok=1 file=[report.rpt] count=5 verbose=1 tags=[a|b] leftover=[extra1|extra2]
--file=r.rpt --noverbose leftover
   -> ok=1 file=[r.rpt] count=0 verbose=0 tags=[] leftover=[leftover]
--tag one --tag two --tag three
   -> ok=1 file=[] count=0 verbose=0 tags=[one|two|three] leftover=[]
(no arguments at all)
   -> ok=1 file=[] count=0 verbose=0 tags=[] leftover=[]

The four shapes, each confirmed by a row above:

"file=s" — a required string value. Both --file report.rpt and --file=r.rpt work; the module accepts either spelling.

"count=i" — a required integer. Use =f for a floating-point value, which is what a slack threshold wants.

"verbose!" — a switch with a negation. The ! gives you --verbose and --noverbose for free. Without the ! you get only --verbose. The negated form is worth having whenever a default is "on", so a user can turn it off.

"tag=s" => \@tags — a repeatable option. Because the target is an array reference, every occurrence is collected: three --tag options gave three elements. Had the target been a scalar, the last one would simply have overwritten the others — which is sometimes what you want and should be a deliberate choice.

And @ARGV keeps what is left. extra1 and extra2 are not options, so they stay. That is how a tool takes both options and a list of files: options are parsed out, filenames remain in @ARGV ready for <>.

3. What Happens When It Goes Wrong

This section matters more than it looks, because the failure is quiet.

Azvya Education Pvt. Ltd.VLSI Mentor
when-it-fails.pl
use strict;
use warnings;
use File::Temp qw(tempdir);
use File::Spec;

my $dir = tempdir(CLEANUP => 1);
my $tool = File::Spec->catfile($dir, "tool.pl");

open my $w, ">", $tool or die "cannot write: $!\n";
print $w <<'PROG';
use strict;
use warnings;
use Getopt::Long;
my ($file, $count) = ("", 0);
my $ok = GetOptions("file=s" => \$file, "count=i" => \$count);
printf "returned %s | file=[%s] count=%d | script continued\n",
    ($ok ? "TRUE" : "FALSE"), $file, $count;
PROG
close $w;

for my $args ('--file good.rpt',
              '--file',
              '--count abc',
              '--bogus') {
    my $out = `cd '$dir' && perl tool.pl $args 2>&1`;
    $out =~ s/\s+\z//;
    $out =~ s/\n/ ;; /g;
    printf "%-16s -> %s\n", $args, $out;
}
Azvya Education Pvt. Ltd.VLSI Mentor
Captured output
--file good.rpt  -> returned TRUE | file=[good.rpt] count=0 | script continued
--file           -> Option file requires an argument ;; returned FALSE | file=[] count=0 | script continued
--count abc      -> Value "abc" invalid for option count (number expected) ;; returned FALSE | file=[] count=0 | script continued
--bogus          -> Unknown option: bogus ;; returned FALSE | file=[] count=0 | script continued

Three different mistakes, three clear warnings — Option file requires an argument, Value "abc" invalid for option count (number expected), Unknown option: bogus.

And in every one of those cases, the script carried on. GetOptions returns false and warns on standard error. It does not die. So this, which is what people write first, is broken:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
GetOptions("file=s" => \$file);          # return value thrown away
process($file);                          # runs with "" after any typo

A user types --fil-e report.rpt, gets a warning they may not see because it went to standard error, and the tool runs against an empty filename. The fix is one word:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
GetOptions("file=s" => \$file) or die "bad options\n";

Better still, print the usage message there, which is Chapter 15.3's subject.

4. The Defaults Are Looser Than You Expect

Here is the behaviour nobody configures and everybody eventually meets.

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

my $dir = tempdir(CLEANUP => 1);

# the SAME options, parsed with the stock defaults and then with them off
my %variant = (
  "stock.pl"  => "",
  "strict.pl" => 'Getopt::Long::Configure("no_auto_abbrev", "no_ignore_case");',
);
for my $name (sort keys %variant) {
    open my $w, ">", File::Spec->catfile($dir, $name) or die "cannot write: $!\n";
    print $w <<"PROG";
use strict;
use warnings;
use Getopt::Long;
$variant{$name}
my (\$file, \$force, \$fast) = ("", 0, 0);
my \$ok = GetOptions("file=s" => \\\$file, "force" => \\\$force, "fast" => \\\$fast);
printf "ok=%d file=[%s] force=%d fast=%d left=[%s]\\n",
    (\$ok ? 1 : 0), \$file, \$force, \$fast, join("|", \@ARGV);
PROG
    close $w;
}

printf "%-14s %-42s %s\n", "ARGUMENTS", "stock defaults", "with both turned off";
for my $args ('--file r.rpt', '--fil r.rpt', '--fi r.rpt',
              '--force', '--FORCE', '-force', '--f r.rpt', '-- --force') {
    my @cell;
    for my $name (qw(stock.pl strict.pl)) {
        my $out = `cd '$dir' && perl $name $args 2>&1`;
        $out =~ s/\s+\z//;
        $out =~ s/\n.*//s;                       # first line only
        $out =~ s/^ok=1 file=\[\] force=(\d) fast=(\d) left=\[\]$/ok force=$1 fast=$2/;
        push @cell, $out;
    }
    printf "%-14s %-42s %s\n", $args, $cell[0], $cell[1];
}
Azvya Education Pvt. Ltd.VLSI Mentor
Captured output
ARGUMENTS      stock defaults                             with both turned off
--file r.rpt   ok=1 file=[r.rpt] force=0 fast=0 left=[]   ok=1 file=[r.rpt] force=0 fast=0 left=[]
--fil r.rpt    ok=1 file=[r.rpt] force=0 fast=0 left=[]   Unknown option: fil
--fi r.rpt     ok=1 file=[r.rpt] force=0 fast=0 left=[]   Unknown option: fi
--force        ok force=1 fast=0                          ok force=1 fast=0
--FORCE        ok force=1 fast=0                          Unknown option: FORCE
-force         ok force=1 fast=0                          ok force=1 fast=0
--f r.rpt      Option f is ambiguous (fast, file, force)  Unknown option: f
-- --force     ok=1 file=[] force=0 fast=0 left=[--force] ok=1 file=[] force=0 fast=0 left=[--force]

Read the left-hand column first — that is the stock behaviour you get from use Getopt::Long; with no configuration:

--fil and --fi are accepted as --file. Abbreviation is on by default. Any unambiguous prefix works.

--f is rejected — but not as unknown. The message names the candidates: Option f is ambiguous (fast, file, force). So the abbreviation only fails once two options collide, which means adding a new option can break a command line that used to work. A script someone has in a Makefile as --fi report.rpt keeps running until the day you add --filter.

--FORCE is accepted as --force. Case is ignored by default for long options.

-force with one dash is accepted, so the single- and double-dash forms are interchangeable for long names.

-- stops option processing. Everything after it is data, so --force lands in @ARGV as a literal string. That is how you pass a filename that begins with a dash.

Now the right-hand column, after one line of configuration:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
Getopt::Long::Configure("no_auto_abbrev", "no_ignore_case");

--fil and --FORCE are now Unknown option. The same commands that worked on the left fail on the right, which is the proof that the configuration did something.

5. The Shape To Copy

Putting it together, this is the opening of a real tool:

Azvya Education Pvt. Ltd.VLSI Mentor
a-real-tool.pl
use strict;
use warnings;
use File::Temp qw(tempdir);
use File::Spec;

my $dir = tempdir(CLEANUP => 1);
my $tool = File::Spec->catfile($dir, "report-slack.pl");

open my $w, ">", $tool or die "cannot write: $!\n";
print $w <<'PROG';
use strict;
use warnings;
use Getopt::Long;

Getopt::Long::Configure("no_auto_abbrev", "no_ignore_case");

# every option gets a default here, so the rest of the script
# never has to ask whether a value was supplied
my %opt = (
    threshold => 0.0,
    check     => "setup",
    outdir    => ".",
    verbose   => 0,
);

GetOptions(\%opt,
    "threshold=f",
    "check=s",
    "outdir=s",
    "verbose!",
    "help",
) or die "run with --help\n";

printf "threshold=%.2f check=%s outdir=%s verbose=%d help=%s files=[%s]\n",
    $opt{threshold}, $opt{check}, $opt{outdir}, $opt{verbose},
    ($opt{help} ? "yes" : "no"), join("|", @ARGV);
PROG
close $w;

for my $args ('--threshold 0.1 --check hold a.rpt b.rpt',
              '--verbose --outdir /tmp/out c.rpt',
              '--help') {
    print `cd '$dir' && perl report-slack.pl $args 2>&1`;
}
Azvya Education Pvt. Ltd.VLSI Mentor
Captured output
threshold=0.10 check=hold outdir=. verbose=0 help=no files=[a.rpt|b.rpt]
threshold=0.00 check=setup outdir=/tmp/out verbose=1 help=no files=[c.rpt]
threshold=0.00 check=setup outdir=. verbose=0 help=yes files=[]

Four things make this version better than the scattered-variables form:

GetOptions(\%opt, "threshold=f", ...) fills one hash. No my variable per option and no \$ in front of each. Passing %opt to a subroutine passes the whole configuration.

Defaults are set before parsing, in the same place, so the list of options and their defaults can be read together. After GetOptions returns, every key has a value and nothing downstream needs // 0.

or die is not optional, for the measured reason in section 3.

--help is just another switch. Chapter 15.3 turns it into a usage message, which is the next thing this tool needs.

One syntax note on that hash form: the option name in the string is the hash key, so "threshold=f" fills $opt{threshold}. If you want a different key, name it explicitly — "max-slack=f" => "threshold" is not valid, but "threshold|max-slack=f" gives the option two spellings that both fill $opt{threshold}. The | is how you add a short form too: "verbose|v!".

6. What To Remember

  • Positional arguments stop working at about four inputs. Named options are self-describing and order-independent.
  • Getopt::Long is core — nothing to install, available everywhere.
  • "name=s" takes a string, "name=i" an integer, "name=f" a float.
  • "name!" gives you --name and --noname.
  • An array target collects a repeated option; a scalar target keeps only the last.
  • Non-option words stay in @ARGV, ready for <>.
  • GetOptions returns false and warns — it does not die. Measured for a missing value, a wrong type and an unknown option.
  • Always write or die, or a typo runs your tool with empty defaults and the warning goes to stderr unseen.
  • Abbreviation is ON by default. Measured: --fi was accepted as --file.
  • An abbreviation can break later — --f reported ambiguous (fast, file, force), so adding an option can break a command line that worked.
  • Case is IGNORED by default. Measured: --FORCE was accepted as --force.
  • One dash works for a long name, and -- stops parsing so the rest is data.
  • Turn both off with Configure("no_auto_abbrev", "no_ignore_case") for anything other scripts call — measured to change the result.
  • Prefer GetOptions(\%opt, ...) with defaults set beforehand, so every key has a value afterwards.
  • "verbose|v!" gives an option a short form; | separates alternative spellings.

Chapter 15.3 handles the half this chapter deliberately left out. Parsing the options is not the same as checking them: a threshold of -5, an output directory that does not exist, or a required input left out all parse perfectly and still cannot be used.

Where this fits

Part of the Perl curriculum.