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 ...
$ 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
#!/usr/bin/env sh echo "$0 is '$0'" i=1 for arg in "$@"; do echo "$$i is '$arg'" i=$((i+1)) done
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
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.
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.)
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
-coption or from the
popen()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.