A frequent quip of the unix-beard is shebangs cannot contain multiple command-line arguments. Let’s break it down and see where this assumption no longer holds true.
§What is a Shebang?
The shebang is the line at the beginning scripts such as Python and Shell
scripts that instructs the OS how to execute the script. Looks something like
#!/bin/sh
or #!/usr/bin/env python
. This line specifies how to interpret
the file. This progam specified on that line is also known as an
interpreter. Without the line, the OS wouldn’t know what to do. By default,
shebang-less scripts are executed with the same program as the parent process.
One might experience this if saving a Perl script without a shebang, then
executing it. Your shell will likely try to run it as a shell script.
This is why a shebang is essential to most scripts. Without one, there is no guarentee it will run with the correct interpreter besides manipulating the parent process to choose a specified interpreter.
Wikipedia has an excellent page on the shebang. The execve(2)
manpage (try
running man 2 execve
) explains the shebang in further detail.
§An example… in Perl?
Consider this short Perl script. It prints the first line and last line, or
just the first line, or no lines. This script uses perl -n
to wrap the body
of the program in while(<>) { ... }
loop. In short, perl -n
reads each
line, then executes the body. Since I need the -n
argument on the command
line, I tried this first:
#!/usr/bin/env perl -n
# Why won't this work :-o
print if ++$lines == 1;
print if eof() && $lines > 1;
If I save it and run it, I get the following error output:
$ /tmp/a.pl < /etc/passwd
/usr/bin/env: ‘perl -n’: No such file or directory
/usr/bin/env: use -[v]S to pass options in shebang lines
GNU coreutils env documentation offers insight:
The
-S/--split-string
option enables use of multiple arguments on the first line of scripts (the shebang line,‘#!’
). … Most operating systems (e.g. GNU/Linux, BSDs) treat all text after the first space as a single argument. When using env in a script it is thus not possible to specify multiple arguments.
Alright, ok. TIL about this -S
flag, neat! If I change the shebang line to
#!/usr/bin/env -Sperl -n
, it works:
$ /tmp/a.pl < /etc/passwd
root:x:0:0:System administrator:/root:/run/current-system/sw/bin/bash
nobody:x:65534:65534:Unprivileged account (don't use!):/var/empty:/run/current-system/sw/bin/nologin
Viola! Passing arguments in the shebang without any funny hacks!
If I add the suggested -v
flag (#!/usr/bin/env -vSperl -n
), env
prints
helpful debug information before executing the script:
split -S: ‘perl -n’
into: ‘perl’
& ‘-n’
executing: perl
arg[0]= ‘perl’
arg[1]= ‘-n’
arg[2]= ‘/tmp/a.pl’
It’s all documented in the coreutils documentation available online or via
info coreutils env
in your terminal.
§Where can I use this feature?
Any modern OS that ships env
from coreutils including the majority of popular
desktop/server Linux distros. If you’re a Mac user you’re also in luck, thanks
to FreeBSD’s downstream env
implementation supporting -S
too.
POSIX env
does not offer this -S
feature. OpenBSD, NetBSD, Busybox, Toybox
are also without -S
. Busybox ships on some Linux distros such as Alpine. My
first step when using these hosts is to install software to reduce the friction
of productivity including coreutils. Coreutils is my portability strategy for
env -S
.
§Can’t use env -S
or need POSIX compliance?
If one cannot use env -S
, I believe the time-tested approach is to write a
wrapper program for each tool that needs specified arguments on the shebang
line. With the above example, I could put this script in my $PATH
and name
it perlloop
:
#!/bin/sh
exec perl -n "$@"
Next, I can update the shebang from the above example to #!/usr/bin/env perlloop
. Then it’ll work the same
§Why not just use #!/usr/bin/perlloop
?
Works too if that’s where perlloop
lives. You probably don’t care where it
lives though. Just want it to find the matching program in your PATH
. That
is why I prefer using #!/usr/bin/env ...
. See the coreutils env
documentation for additional discussion on the tradeoffs.
§Emacs and Shebangs
My Emacs setup checks for a shebang, and ensures the file is executable on save. Check it out here. I suggest adding a hook to your text editor to ensure your scripts are executable on save. It saves a lot of headache.
I also wrote an experimental feature to auto-matically changes the syntax
highlighting and major mode when the shebang changes in the Emacs buffer. The
result is not fiddling about with distracting stuff like re-opening the file or
using M-: (auto-mode) RET
.
Shebangs are pretty cool.