On Linux, the shebang isn't very flexible; according to multiple answers (Stephen Kitt's answer and Jörg W Mittag's), there is no designated way to pass multiple arguments in a shebang line.
I'm not sure if it will be of use to anyone, but I've written a short script to implement the lacking feature. See https://gist.github.com/loxaxs/7cbe84aed1c38cf18f70d8427bed1efa.
It is also possible to write embedded workarounds. Bellow, I present four language-agnostic workarounds applied to the same test script and the result each prints. I suppose that the script is executable and is in /tmp/shebang
.
Wrapping your script in a bash heredoc inside process substitution
As far as I know, this is the most reliable language-agnostic way of doing it. It allows passing arguments and preserves stdin. The drawback is that the interpreter doesn't know the (real) location of the file it reads.
#!/bin/bash
exec python3 -O <(cat << 'EOWRAPPER'
print("PYTHON_SCRIPT_BEGINNING")
from sys import argv
try:
print("input() 0 ::", input())
print("input() 1 ::", input())
except EOFError:
print("input() caused EOFError")
print("argv[0] ::", argv[0])
print("argv[1:] ::", argv[1:])
print("__debug__ ::", __debug__)
# The -O option changes __debug__ to False
print("PYTHON_SCRIPT_END")
EOWRAPPER
) "$@"
Calling echo -e 'aa\nbb' | /tmp/shebang 'arg1' 'arg2 contains spaces' 'arg3\ uses\ \\escapes\\'
prints:
PYTHON_SCRIPT_BEGINNING
input() 0 :: aa
input() 1 :: bb
argv[0] :: /dev/fd/62
argv[1:] :: ['arg1', 'arg2 contains spaces', 'arg3\\ uses\\ \\\\escapes\\\\']
__debug__ :: False
PYTHON_SCRIPT_END
Note that process substitution produces a special file. This may not suit all executables. For instance, #!/usr/bin/less
complains: /dev/fd/63 is not a regular file (use -f to see it)
I don't know if it is possible to have heredoc inside process substitution in dash.
Wrapping your script in a simple heredoc
Shorter and simpler, but you won't be able to access stdin
from your script and it requires the interpreter to be able to read and execute a script from stdin
.
#!/bin/sh
exec python3 - "$@" << 'EOWRAPPER'
print("PYTHON_SCRIPT_BEGINNING")
from sys import argv
try:
print("input() 0 ::", input())
print("input() 1 ::", input())
except EOFError:
print("input() caused EOFError")
print("argv[0] ::", argv[0])
print("argv[1:] ::", argv[1:])
print("__debug__ ::", __debug__)
# The -O option changes __debug__ to False
print("PYTHON_SCRIPT_END")
EOWRAPPER
Calling echo -e 'aa\nbb' | /tmp/shebang 'arg1' 'arg2 contains spaces' 'arg3\ uses\ \\escapes\\'
prints:
PYTHON_SCRIPT_BEGINNING
input() caused EOFError
argv[0] :: -
argv[1:] :: ['arg1', 'arg2 contains spaces', 'arg3\\ uses\\ \\\\escapes\\\\']
__debug__ :: True
PYTHON_SCRIPT_END
Use awk system()
call but without arguments
Correctly passes the name of the executed file, but your script won't receive the arguments you give it.
Note that awk is the only language I know whose interpreter both is installed on linux by default and reads its instructions from the command line by default.
#!/usr/bin/gawk BEGIN {system("python3 -O " ARGV[1])}
print("PYTHON_SCRIPT_BEGINNING")
from sys import argv
print("input() 0 ::", input())
print("input() 1 ::", input())
print("argv[0] ::", argv[0])
print("argv[1:] ::", argv[1:])
print("__debug__ ::", __debug__)
# The -O option changes __debug__ to False
print("PYTHON_SCRIPT_END")
Calling echo -e 'aa\nbb' | /tmp/shebang 'arg1' 'arg2 contains spaces' 'arg3\ uses\ \\escapes\\'
prints:
PYTHON_SCRIPT_BEGINNING
input() 0 :: aa
input() 1 :: bb
argv[0] :: /tmp/shebang
argv[1:] :: []
__debug__ :: False
PYTHON_SCRIPT_END
Use awk 4.1+ system()
call, provided your arguments do not contain spaces
Nice, but only if you are sure your script won't be called with arguments containing spaces. As you can see, your arguments containing spaces would be split, unless the spaces are escaped.
#!/usr/bin/gawk @include "join"; BEGIN {system("python3 -O " join(ARGV, 1, ARGC, " "))}
print("PYTHON_SCRIPT_BEGINNING")
from sys import argv
print("input() 0 ::", input())
print("input() 1 ::", input())
print("argv[0] ::", argv[0])
print("argv[1:] ::", argv[1:])
print("__debug__ ::", __debug__)
# The -O option changes __debug__ to False
print("PYTHON_SCRIPT_END")
Calling echo -e 'aa\nbb' | /tmp/shebang 'arg1' 'arg2 contains spaces' 'arg3\ uses\ \\escapes\\'
prints:
PYTHON_SCRIPT_BEGINNING
input() 0 :: aa
input() 1 :: bb
argv[0] :: /tmp/shebang
argv[1:] :: ['arg1', 'arg2', 'contains', 'spaces', 'arg3 uses \\escapes\\']
__debug__ :: False
PYTHON_SCRIPT_END
For awk versions below 4.1, you will have to use string concatenation inside a for loop, see example function https://www.gnu.org/software/gawk/manual/html_node/Join-Function.html .
#!/bin/sh -
instead of#!/usr/bin/env sh
.#!/bin/sh -
is the most portable hashbang line./bin/sh
is more likely to exist than/usr/bin/env
. Only use#!/usr/bin/env foo
is for everyfoo
that is notsh
.