Specifying a relative interpreter in a shebang
Background
In order to make a Python script executable it needs to have a shebang line specifying the interpreter of the script.
This enables passing the script directly to execve
where the kernel can eventually call /usr/bin/python3
with the script file as an argument. This works well but has the drawback of harcoding the interpreter. If a different interpreter is needed, like one located in a virtual environment, it cannot be used.
Using env
Instead of hardcoding an interpreter, the shebang line can use env(1)
. Passing env
an argument cases env
to lookup that name in the $PATH
and execute that as the interpreter.
The user can then move their desired python3
binary to the front of the $PATH
and that will be used to execute the script.
Limitations of env
There are two limitations with using env
like above. First, it’s not possible to pass arguments to python3
via env
because env
interprets the additional arguments as the name of the interpreter. For example trying to pass the -E
flag to python3
results in the following error.
This is because env
does not split the arguments by spaces before passing them to execve
.
Second, this approach assumes the desired interpreter is on the front of the $PATH
. If it’s not possible to put the desired interpreter on the $PATH
then the correct interpreter will not be used.
Using env -S
It’s possible to overcome the above limitations by using the -S
flag to env
. The documentation explains that the -S
flag will split arguments on whitespace and pass them all to execve
. This means instead of just specifying a binary name, all of the values of execve
can be specified, such as specifying -E
to the interpreter.
This can be verified by passing -v
to get verbose output from env
.
Selecting a relative interpreter
Since the -S
flag allows us to pass full arguments to an interpreter, it’s possible then to encode an entire program in the shebang line as an argument to another interpreter and have that execute our desired interpreter.
For example we could use env -S
to execute /bin/sh
and pass in a full script to -c
, where that script locates the desired interpreter relative to the script and then executes that. The documentation explains that spaces can be escaped with \_
.
Conclusion
By combining env -S
and sh
it’s possible to create a shebang line that executes an interpreter relative to the script. This could be used to have a script execute in a specific virtual environment.