#! /bin/sh -
Is (or at least was) often the recommended shebang to have a script interpreted by
Why not just
#! /bin/sh or
That's for a similar reason as why you need to write:
rm -- *.txt
Unless you can guarantee none of the
.txt files in the current directory have a name that starts with
<arg> is considered an option if it starts with
- or a file to remove otherwise. In
rm -- <arg>
arg is always considered as a file to remove regardless of whether it starts with
- or not.
That's the same for
When one executes a script that starts with
execve("path/to/the-script", ["the-script", "arg"], [environ])
The system transforms it to:
execve("/bin/sh", ["/bin/sh", "path/to/the-script", "arg"], [environ])
path/to/the-script is not usually something that is under the control of the script author. The author can't predict where copies of the script will be stored nor under what name. In particular, they can't guarantee that the
path/to/the-script with which it is called won't start with
+ which is also a problem with
sh). That's why we need the
- here to mark the end of options.
For instance, on my system
zcat (like most other scripts actually) is one example of a script that didn't follow that recommendation:
$ head -n1 /bin/zcat #!/bin/sh $ mkdir + $ ln -s /bin/zcat +/ $ +/zcat /bin/sh: +/: invalid option [...]
Now you may ask why
#! /bin/sh - and not
#! /bin/sh --?
#! /bin/sh -- would work with POSIX shells, the
#! /bin/sh - is more portable; in particular to ancient versions of
- as and end-of-option predates
getopt() and the general use of
-- to mark the end of options by a long time. The way the Bourne shell (from the late 70s) parsed its arguments, only the first argument was considered for options if it started with
-. All the characters after
- would be treated
as option names; if there was no character after the
-, there was no options. That stuck and all later Bourne-like shells recognise
- as a way to mark the end of options.
In the Bourne shell (but not in modern Bourne-like shells),
#! /bin/sh -euf would also work around the problem as only the first argument was considered for options.
Now, one could say that we're being pedantic here, and it's also why I wrote the need in italic above:
+or put them in a directory whose name starts with
execlp()-type functions. And in that case, generally you invoke them either as
the-scriptfor it to be looked up in
$PATHin which case the path argument to the
execve()system call will typically start with
+) or as
./the-scriptif you want
the-scriptin the current directory to be run (and then the path starts with
Now beside the theoretical correctness issue, there's another reason why
#! /bin/sh - became recommended as good practice. And that goes back to a time where several systems still supported setuid scripts.
If you have a script that contains:
#! /bin/sh /bin/echo "I'm running as root"
And that script was setuid root (like with
-r-sr-xr-x root bin permissions), on those systems, when executed by an ordinary user, the
execve("/bin/sh", ["/bin/sh", "path/to/the-script"], [environ])
would be done as
If the user created a symlink
/tmp/-i -> path/to/the-script and executed it as
-i, then it would start an interactive shell (
/bin/sh -i) as
- would work around that (it would not work around the race-condition issue, or the fact that some
sh implementations like some
ksh88-based ones would lookup script arguments without
Nowadays, hardly any system support setuid script any longer, and some of those that still do (usually not by default), end up doing a
execve("/bin/sh", ["/bin/sh", "/dev/fd/<n>", arg]) (where
<n> is a file descriptor open for reading on the script) which works around both this issue and the race condition.
Note that you get similar issues with most interpreters, not only Bourne-like shells. Non-Bourne-like shells generally don't support
- as the end-of-option marker, but generally support
-- instead (at least for modern versions).
#! /usr/bin/awk -f #! /usr/bin/sed -f
Don't have the issue as the next argument is considered as an argument to the
-f option in any case, but it still doesn't work if
- (in which case, with most
awk don't read the code from the
- file, but from stdin instead).
Also note that on most systems, one can't use:
#! /usr/bin/env sh - #! /usr/bin/perl -w --
As on most systems the shebang mechanism allows only one argument after the interpreter path.
As to whether to use
#! /bin/sh - vs
#!/bin/sh -, that's just a matter of taste. I prefer the former as it makes the interpreter path more visible and makes mouse selection easier. There's a legend that says that the space was needed in some ancient Unix versions but AFAIK, that has never been verified.
A very good reference about the Unix shebang can be found at https://www.in-ulm.de/~mascheck/various/shebang