Multiple arguments in shebang
Solution 1
There is no general solution, at least not if you need to support Linux, because the Linux kernel treats everything following the first “word” in the shebang line as a single argument.
I’m not sure what NixOS’s constraints are, but typically I would just write your shebang as
#!/bin/bash --posix
or, where possible, set options in the script:
set -o posix
Alternatively, you can have the script restart itself with the appropriate shell invocation:
#!/bin/sh -
if [ "$1" != "--really" ]; then exec bash --posix -- "$0" --really "$@"; fi
shift
# Processing continues
This approach can be generalised to other languages, as long as you find a way for the first couple of lines (which are interpreted by the shell) to be ignored by the target language.
GNU coreutils
’ env
provides a workaround since version 8.30, see unode’s answer for details. (This is available in Debian 10 and later, RHEL 8 and later, Ubuntu 19.04 and later, etc.)
Solution 2
Although not exactly portable, starting with coreutils 8.30 and according to its documentation you will be able to use:
#!/usr/bin/env -S command arg1 arg2 ...
So given:
$ cat test.sh
#!/usr/bin/env -S showargs here 'is another' long arg -e "this and that " too
you will get:
% ./test.sh
$0 is '/usr/local/bin/showargs'
$1 is 'here'
$2 is 'is another'
$3 is 'long'
$4 is 'arg'
$5 is '-e'
$6 is 'this and that '
$7 is 'too'
$8 is './test.sh'
and in case you are curious showargs
is:
#!/usr/bin/env sh
echo "\$0 is '$0'"
i=1
for arg in "$@"; do
echo "\$$i is '$arg'"
i=$((i+1))
done
Solution 3
The POSIX standard is very terse on describing #!
:
From the rationale section of the documentation of the exec()
family of system interfaces:
Another way that some historical implementations handle shell scripts is by recognizing the first two bytes of the file as the character string
#!
and using the remainder of the first line of the file as the name of the command interpreter to execute.
From the Shell Introduction section:
The shell reads its input from a file (see
sh
), from the-c
option or from thesystem()
andpopen()
functions defined in the System Interfaces volume of POSIX.1-2008. If the first line of a file of shell commands starts with the characters#!
, the results are unspecified.
This basically means that any implementation (the Unix you are using) is free to do the specifics of the parsing of the shebang line as it wants.
Some Unices, like macOS (can't test ATM), will split the arguments given to the interpreter on the shebang line into separate arguments, while Linux and most other Unices will give the arguments as a single option to the interpreter.
It is thus unwise to rely on the shebang line being able to take more than a single argument.
See also the Portability section of the Shebang article on Wikipedia.
One easy solution, which is generalizable to any utility or language, is to make a wrapper script that executes the real script with the appropriate command line arguments:
#!/bin/sh
exec /bin/bash --posix /some/path/realscript "$@"
I don't think I would personally try to make it re-execute itself as that feels somewhat fragile.
Solution 4
The shebang is described in execve
(2) man page as follow:
#! interpreter [optional-arg]
Two spaces are accepted in this syntax:
- One space before the interpreter path, but this space is optional.
- One space separating the the interpreter path and its optional argument.
Note that I didn't used the plural when talking of an optional argument, neither does the syntax above uses [optional-arg ...]
, as you can provide at most one single argument.
As far as shell scripting is concerned, you can use the set
built-in command near the beginning of your script which will allow to set interpreters parameters, providing the same result as if you used command-line arguments.
In your case:
set -o posix
From a Bash prompt, check the output of help set
to get all available options.
Solution 5
I found a rather stupid workaround when looking for an executable that accepts a script as a single argument:
#!/usr/bin/awk BEGIN{system("bash --posix "ARGV[1])}
Related videos on Youtube
Rastapopoulos
Updated on September 18, 2022Comments
-
Rastapopoulos almost 2 years
I am wondering whether there is a general way of passing multiple options to an executable via the shebang line (
#!
).I use NixOS, and the first part of the shebang in any script I write is usually
/usr/bin/env
. The problem I encounter then is that everything that comes after is interpreted as a single file or directory by the system.Suppose, for example, that I want to write a script to be executed by
bash
in posix mode. The naive way of writing the shebang would be:#!/usr/bin/env bash --posix
but trying to execute the resulting script produces the following error:
/usr/bin/env: ‘bash --posix’: No such file or directory
I am aware of this post, but I was wondering whether there was a more general and cleaner solution.
EDIT: I know that for Guile scripts, there is a way to achieve what I want, documented in Section 4.3.4 of the manual:
#!/usr/bin/env sh exec guile -l fact -e '(@ (fac) main)' -s "$0" "$@" !#
The trick, here, is that the second line (starting with
exec
) is interpreted as code bysh
but, being in the#!
...!#
block, as a comment, and thus ignored, by the Guile interpreter.Would it not be possible to generalize this method to any interpreter?
Second EDIT: After playing around a little bit, it seems that, for interpreters that can read their input from
stdin
, the following method would work:#!/usr/bin/env sh sed '1,2d' "$0" | bash --verbose --posix /dev/stdin; exit;
It's probably not optimal, though, as the
sh
process lives until the interpreter has finished its job. Any feedback or suggestion would be appreciated.-
Kusalananda over 6 years
-
Stephen Kitt over 6 years
-
-
Stephen Kitt over 6 yearsYou’re allowed to have more than two spaces, they’re just considered to be part of the optional argument.
-
WhiteWinterWolf over 6 years@StephenKitt: Indeed, white space here is to be taken more as a category than the actual space char. I suppose that other white spaces such as tabs should also be widely accepted.
-
John McGehee over 5 yearsThis is very good to know for future reference.
-
Stéphane Chazelas over 5 yearsThat option was copied from FreeBSD's
env
where-S
was added in 2005. See lists.gnu.org/r/coreutils/2018-04/msg00011.html -
Eric over 5 yearsWorks a treat on Fedora 29
-
Eric over 5 years
-
Kusalananda about 5 yearsRelated (similar): How does this shebang that starts with a double hyphen (--) work?
-
tcoolspy about 5 yearsGiven that an empty string is … um … empty, you should be able to drop your command not found monkey business:
''''exec ...
should get the job done. Note no space before exec or it will make it look for the empty command. You want to splice the empty onto the first arg so the so$0
isexec
. -
chocolateboy almost 5 yearsFYI: as of coreutils 8.31,
env
includes its ownshowargs
: the -v option e.g.#!/usr/bin/env -vS --option1 --option2 ...
-
Kokizzu about 4 yearsdoesnt work on ubuntu 18.04.3
-
Szczepan Hołyszewski about 4 yearsThis circumvents the problem instead of solving it.
-
Lacek over 3 years@Kokizzu The first series of Ubuntu to include coreutils 8.30 is 19.04. That's why it doesn't work on 18.04.
-
Jeremy Boden about 3 yearsA script that runs an arbitrary command is a bit dangerous.
-
Shining Trapezohedron about 3 yearsUnless I'm misunderstanding what you mean here, It won't run an arbitrary command, only the one specified in "COMMAND_HERE". Anything specified as a parameter is interpreted as an argument so can't be used to inject arbitrary commands (As far as I know, I did do some testing for this). If you object to the use of awk to run an arbitrary command, you can do the same with shebangs in general, admittedly, this is harder to read, but that's just a necessity of working around the limitations of shebangs.
-
C. M. about 3 yearsI recommend against anything more complex than
#!/bin/bash
,#!/bin/env bash
, or similar as a shebang. If you need to do more complex processing or argument/parameter processing, do it in the body of the script, not the shebang. Reasons for my recommendation include things like: 1) shebang lines may be limited or truncated, 2) some shebangs can be accidentally or intentionally broken by certain arguments, 3) during the shebang evaluation part, the environment may not be fully set up as needed, and other reasons, too long or many to post in a comment.