#! /bin/sh -
Is (or at least was) often the recommended shebang to have a script interpreted by /bin/sh
(or #! /bin/bash -
for bash, #! /bin/ksh -
for ksh, etc).
Why not just #! /bin/sh
or #!/bin/sh
?
What's that -
for?
#! /bin/sh -
Is (or at least was) often the recommended shebang to have a script interpreted by /bin/sh
(or #! /bin/bash -
for bash, #! /bin/ksh -
for ksh, etc).
Why not just #! /bin/sh
or #!/bin/sh
?
What's that -
for?
That's for a similar reason as why you need to write:
rm -- *.txt
And not
rm *.txt
Unless you can guarantee none of the .txt
files in the current directory have a name that starts with -
.
In:
rm <arg>
<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 sh
.
When one executes a script that starts with
#! /bin/sh
Typically 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])
The 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 -
(or +
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 --
?
While #! /bin/sh --
would work with POSIX shells, the #! /bin/sh -
is more portable; in particular to ancient versions of sh
. sh
treating -
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 -eu
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 +
or put them in a directory whose name starts with -
or +
.execvp()
/execlp()
-type functions. And in that case, generally you invoke them either as the-script
for it to be looked up in $PATH
in which case the path argument to the execve()
system call will typically start with /
(not -
nor +
) or as ./the-script
if you want the-script
in the current directory to be run (and then the path starts with ./
, not -
nor +
either).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 root
!
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 root
.
The -
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 /
in $PATH
though).
Nowadays, hardly any system support setuid scripts 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).
Also
#! /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 path/to/script
is -
(in which case, with most sed
/awk
implementations, sed
/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
¹ Though with zsh
, you can do #! /bin/zsh -eu-
where the extra -
there also marks the end of options.
bash
accepts options like every other shell. Being a Bourne-like and POSIX shell,bash
accepts both#! /bin/bash -
and#! /bin/bash --
. – Stéphane Chazelas Mar 19 '17 at 22:03#! /bin/sh
(https://unix.stackexchange.com/search?q=user%3A22565+code%3A%22%23%21+%2Fbin%2Fsh%22) and 152 containing#! /bin/sh -
(https://unix.stackexchange.com/search?q=user%3A22565+code%3A%22%23%21+%2Fbin%2Fsh+-%22). There are 6 posts that may be not using the-
. – QuartzCristal Sep 07 '22 at 14:41#! /usr/bin/env bash
ones which we can't do anything about portably as most systems don't allow more than on argument for the interpreter. – Stéphane Chazelas Sep 07 '22 at 14:46#! /usr/bin/env sh -
as a valid point against the use ofenv
in the shebang? – Christopher Mar 12 '24 at 11:23sh
where these days you can expect a POSIX /bin/sh as much as you can expect a /usr/bin/env), when balanced against the possible benefits of using it (or drawback of not using it or using more clunky alternatives), it might have little weight. – Stéphane Chazelas Mar 12 '24 at 12:47