<!-- category: hidden-gems -->
The -s Shebang Switch for Instant CLI Arguments
You need a quick script that takes a flag. Maybe--verbose.
Maybe --output=file.txt. The responsible way is Getopt::Long.
The fast way is -s.
Run it:#!/usr/bin/perl -s use strict; use warnings; our ($verbose, $output); print "Verbose mode ON\n" if $verbose; print "Output: $output\n" if $output;
No$ ./script.pl -verbose -output=report.txt Verbose mode ON Output: report.txt
use Getopt::Long. No GetOptions() call. No option spec. No
hash. No error handling boilerplate. The -s switch tells Perl to
scan @ARGV for switches, strip them out, and create package
variables with matching names. That is the entire argument parsing
step, done before your code even starts.
Part 1: HOW IT WORKS
The-s flag goes on the shebang line:
When Perl sees#!/usr/bin/perl -s
-s, it processes @ARGV before executing your
script. For each argument that starts with -, Perl:
- Strips the leading
- - Creates a package variable with that name
- Sets it to
1(boolean flag) or the value after= - Removes the argument from
@ARGV
Processing stops at the first argument that does not start with
-, or at --.
After processing,ARGUMENT VARIABLE CREATED VALUE -------------- ----------------- -------- -verbose $verbose 1 -debug $debug 1 -output=foo $output "foo" -count=42 $count "42" -x $x 1
@ARGV contains only the remaining non-switch
arguments. Your script sees a clean argument list.
Part 2: BOOLEAN FLAGS
The simplest use. A flag is either present or absent:#!/usr/bin/perl -s use strict; use warnings; use feature 'say'; our ($verbose, $dry_run, $force); say "Verbose mode" if $verbose; say "Dry run mode" if $dry_run; say "Force mode" if $force; # remaining args are files or whatever say "Files: @ARGV" if @ARGV;
The variables are either$ ./script.pl -verbose -force file1.txt file2.txt Verbose mode Force mode Files: file1.txt file2.txt
1 (flag present) or undef (flag
absent). With our declarations and strict mode, you get clean
boolean behavior with no warnings.
Part 3: STRING AND NUMERIC VALUES
Use= to pass a value:
#!/usr/bin/perl -s use strict; use warnings; use feature 'say'; our ($name, $count, $sep); $name //= "world"; $count //= 1; $sep //= ", "; for (1 .. $count) { say "Hello${sep}${name}!"; }
The$ ./script.pl -name=Perl -count=3 Hello, Perl! Hello, Perl! Hello, Perl! $ ./script.pl -name=Dave -sep=" dear " Hello dear Dave! $ ./script.pl Hello, world!
//= operator provides defaults. If the flag is not passed,
the variable is undef, and the default kicks in. Clean fallback
behavior.
Note that values are always strings. If you need a number, Perl's
automatic string-to-number conversion handles it. -count=3
gives you the string "3", but 1 .. $count works fine because
Perl numifies it.
Part 4: THE OUR DECLARATION
Withuse strict, you must declare the variables with our:
Withoutour ($verbose, $output, $count);
our, strict mode complains about undeclared globals.
Without strict mode, the variables are created as package globals
automatically. But you should always use strict. So always use
our.
You can declare them individually or in a list:
These are package variables, not lexical (our $verbose; our $output; our $count; # or all at once our ($verbose, $output, $count);
my) variables. They
live in the current package's symbol table. For a standalone
script in main::, this is fine. In a module, be careful. You
probably do not want CLI switches polluting a module's namespace.
Part 5: QUICK AND DIRTY SCRIPTS
This is where-s shines. Scripts you write in 30 seconds that
need one or two flags:
Grep with optional line numbers:
#!/usr/bin/perl -s use strict; use warnings; our ($n, $i); my $pattern = shift or die "Usage: $0 [-n] [-i] pattern files...\n"; my $flags = $i ? 'i' : ''; my $re = qr~(?$flags)$pattern~; while (<>) { if (m~$re~) { print $n ? "$ARGV:$.: $_" : $_; } }
Quick file renamer:$ ./mygrep.pl -n -i error /var/log/*.log
#!/usr/bin/perl -s use strict; use warnings; use feature 'say'; use File::Copy 'move'; our ($dry, $from, $to); die "Usage: $0 -from=PATTERN -to=REPLACEMENT files...\n" unless $from && $to; for my $file (@ARGV) { (my $new = $file) =~ s~$from~$to~; if ($new ne $file) { if ($dry) { say "Would rename: $file -> $new"; } else { move($file, $new) or warn "Failed: $file: $!\n"; say "Renamed: $file -> $new"; } } }
These scripts take 60 seconds to write. Adding Getopt::Long would triple the code for no practical benefit.$ ./rename.pl -dry -from='\.txt$' -to='.md' *.txt Would rename: notes.txt -> notes.md Would rename: todo.txt -> todo.md
Part 6: HOW @ARGV IS MODIFIED
The-s processing removes switches from @ARGV, leaving only
positional arguments:
#!/usr/bin/perl -s use strict; use warnings; use feature 'say'; our ($verbose, $output); say "Flags:"; say " verbose = " . ($verbose // 'undef'); say " output = " . ($output // 'undef'); say "Remaining ARGV:"; say " $_" for @ARGV;
The$ ./script.pl -verbose -output=log.txt file1 file2 file3 Flags: verbose = 1 output = log.txt Remaining ARGV: file1 file2 file3
-- double-dash stops switch processing:
Everything after$ ./script.pl -verbose -- -not-a-flag file1 Flags: verbose = 1 Remaining ARGV: -not-a-flag file1
-- stays in @ARGV as-is, even if it starts
with -. This is the standard Unix convention.
Part 7: LIMITATIONS
Time for honesty. The-s switch has real limitations:
No long options. Only single-dash flags. No --verbose. If
you pass --verbose, Perl creates a variable called $verbose
(stripping both dashes), but users expect --long and -short
to be different, and -s does not make that distinction.
No validation. Typo a flag name? Perl happily creates a
variable you never check. -verbos does not trigger an error.
It just creates $verbos and nobody notices.
No type checking. Everything is a string. -count=banana is
perfectly valid. You get "banana" in $count and a runtime
explosion when you try to use it as a number.
No bundling. -abc creates a variable $abc, not three
variables $a, $b, $c. No way to combine single-character
flags.
No negation. No --no-verbose or -noverbose. You have to
handle that yourself.
No help generation. Getopt::Long with Pod::Usage gives you
--help for free. With -s, you write your own usage message.
LIMITATION WORKAROUND --------------------- ---------------------------------- No validation Check defined() and die with usage No type checking Validate manually after parsing No long options Accept it or switch to Getopt No bundling Accept it No help generation Write a usage() sub
Part 8: WHEN TO USE -S VS GETOPT::LONG
The decision is simple:There is a middle ground. Start withUSE -s WHEN: * Script is < 50 lines * 1-4 simple flags * Personal use or team tooling * Throwaway / prototyping * You want zero boilerplate USE Getopt::Long WHEN: * Script is a real tool others will use * Complex option types (arrays, hashes) * You need --help auto-generation * Long and short option forms (--verbose / -v) * Required options with validation * You care about typo detection
-s for the prototype. If
the script survives long enough to need proper argument handling,
refactor to Getopt::Long. The variable names usually stay the
same. The switch is mechanical.
The bodies of the scripts are identical. Only the argument parsing preamble changes.# -s version our ($verbose, $output); # Getopt::Long version use Getopt::Long; my ($verbose, $output); GetOptions( 'verbose' => \$verbose, 'output=s' => \$output, ) or die "Bad options\n";
Part 9: COMBINING WITH OTHER SHEBANG FLAGS
You can combine-s with other Perl flags on the shebang line:
That gives you#!/usr/bin/perl -sw
-s (switch parsing) and -w (warnings). You
can also combine with -l (auto-chomp and auto-newline):
#!/usr/bin/perl -sl our $upper; while (<>) { print $upper ? uc : $_; }
Common combinations:$ echo -e "hello\nworld" | ./script.pl -upper HELLO WORLD
Be careful withFLAGS MEANING -------- ------------------------------------ -s Switch parsing only -sw Switches + warnings -sl Switches + auto line handling -swl Switches + warnings + line handling -sn Switches + implicit while(<>) loop
-sn and -sp. The -n and -p flags wrap
your code in a while (<>) loop, which changes how the script
behaves. The -s processing still happens first, before the
loop starts.
Part 10: THE PHILOSOPHY OF QUICK
Perl was designed for getting things done. Larry Wall called it the "duct tape of the Internet." The-s switch is duct tape for
argument parsing.
It is not robust. It is not safe. It does not generate help text or validate input. But it does one thing brilliantly: it gets arguments into your script with zero ceremony.
When you are writing a script at 11 PM to fix a problem right now,
you do not need a 15-line Getopt::Long preamble. You need -s
and two our declarations and the problem solved before midnight.
The.--. |o_o | "Life's too short for |:_/ | GetOptions()." // \ \ (| | ) /'\_ _/`\ \___)=(___/
-s switch has been in Perl since version 1. It is older
than most Perl programmers. It is not deprecated. It is not going
away. It sits there on your shebang line, ready to parse your
arguments in zero lines of code.
For quick scripts, disposable tools, and late-night fixes, it is perfect. For anything else, use Getopt::Long. Knowing when to use which is the mark of a pragmatic Perl programmer.
Keep -s in your back pocket. You will reach for it more often
than you think.
perl.gg