Shebang Arguments – Using Multiple Arguments in Shebang

argumentsenvironment-variablesposixscriptingshebang

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 by sh 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.

Best Answer

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 coreutilsenv 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.)