shebang: use interpreter relative to the script path shebang: use interpreter relative to the script path python python

shebang: use interpreter relative to the script path


There is a healthy set of multi-line shebang scripts on this page for a lot of languages, example:

#!/bin/sh"exec" "`dirname $0`/python" "$0" "$@"print copyright

And if you want one-line shebang, this answer (and question) explains the issue in the details and suggests the following approaches using additional scripts inside the shebang:

Using AWK

#!/usr/bin/awk BEGIN{a=ARGV[1];sub(/[a-z_.]+$/,"python",a);system(a"\t"ARGV[1])}

Using Perl

#!/usr/bin/perl -e$_=$ARGV[0];exec(s/\w+$/python/r,$_)

update from 11Jan21:

Using updated env utility:

$ env --version | grep envenv (GNU coreutils) 8.30$ env --helpUsage: env [OPTION]... [-] [NAME=VALUE]... [COMMAND [ARG]...]Set each NAME to VALUE in the environment and run COMMAND.Mandatory arguments to long options are mandatory for short options too.  -i, --ignore-environment  start with an empty environment  -0, --null           end each output line with NUL, not newline  -u, --unset=NAME     remove variable from the environment  -C, --chdir=DIR      change working directory to DIR  -S, --split-string=S  process and split S into separate arguments;                        used to pass multiple arguments on shebang lines

So, passing -S to env will do the job now


Expanding @Anton's answer so as not to fail on spaces and other special characters in the path and to explain a bit more about the magic.

#!/bin/sh"true" '''\'exec "$(dirname "$(readlink -f "$0")")"/venv/bin/python "$0" "$@"'''__doc__ = """You will need to deliberately set your docstrings though"""print("This script is interpretable by python and sh!")

This clever script is comprehendable by both sh and python. Each of which react to it differently. The first 2 lines after the shebang are interpreted by sh and cause it to hand over exec to a relative python binary (supplied with the same command line arguments). These same lines are safely discarded by python since they amount to a string ("true") followed by a multi-line string (''').

A little more about this subject can be read here.


After looking at these answers, I decided to use a Python-specific solution that does not actually change the shebang.

This solution uses the system python interpreter to find the desired interpreter and execute that. This is useful to me because it allows me to munge environment variables as well as ensuring the correct interpreter.

Because it uses exec, it does not double the memory use-- the new interpreter replaces the last one. Also, exit status and signals will be handled correctly.

It is a python module that is loaded by the script that needs to run under this environment. Importing this module has the side-effect of launching a new interpreter if needed. Generally, modules should not have side-effects, but the alternative is to run the module's function before non-system imports are performed, and that would be a PEP8 violation. So you have to pick your poison.

"""Ensure that the desired python environment is running."""import osimport sysdef ensure_interpreter():    """Ensure we are running under the correct interpreter and environ."""    abs_dir = os.path.dirname(os.path.abspath(__file__))    project_root = os.path.normpath(os.path.join(abs_dir, '../../../'))    desired_interpreter = os.path.join(project_root, 'bin/python')    if os.path.abspath(sys.executable) == desired_interpreter:        return    env = dict(os.environ)    def prefix_paths(key, prefix_paths):        """Add prefix paths, relative to the project root."""        new_paths = [os.path.join(project_root, p) for p in prefix_paths]        new_paths.extend(env.get(key, '').split(':'))        env[key] = ':'.join(new_paths)    prefix_paths('PYTHONPATH', ['dir1', 'dir2'])    prefix_paths('LD_LIBRARY_PATH', ['lib'])    prefix_paths('PYTHON_EGG_CACHE', ['var/.python-eggs'])    env['PROJECT_ROOT'] = project_root    os.execvpe(desired_interpreter, [desired_interpreter] + sys.argv, env)ensure_interpreter()

If you don't need to munge any environment variables, you can strip out everything between env = dict(os.environ) and os.execvpe(desired_interpreter, [desired_interpreter] + sys.argv, env).