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:
report-slack.pl setup.rpt 0.1Add three more inputs and it becomes unreadable:
report-slack.pl setup.rpt 0.1 /tmp/out 1 0 holdNobody 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:
report-slack.pl --input setup.rpt --threshold 0.1 --outdir /tmp/out --verbose --check holdGetopt::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.
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;
}--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.
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;
}--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 continuedThree 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:
GetOptions("file=s" => \$file); # return value thrown away
process($file); # runs with "" after any typoA 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:
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.
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];
}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:
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:
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`;
}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::Longis core — nothing to install, available everywhere."name=s"takes a string,"name=i"an integer,"name=f"a float."name!"gives you--nameand--noname.- An array target collects a repeated option; a scalar target keeps only the last.
- Non-option words stay in
@ARGV, ready for<>. GetOptionsreturns 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:
--fiwas accepted as--file. - An abbreviation can break later —
--freportedambiguous (fast, file, force), so adding an option can break a command line that worked. - Case is IGNORED by default. Measured:
--FORCEwas 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.
