How does a Unix shebang line decide which Python interpreter runs a script?
answer
- The operating system reads the first line
- It needs the execute bit set
- Absolute path, or an indirection through PATH
- Ignored when you name the interpreter yourself
- Installed tools carry a fixed absolute path
basics
~20 sWhen an executable file starts with #!, the kernel runs the interpreter named on that line and passes the script as an argument. #!/usr/bin/python3 pins one absolute interpreter; #!/usr/bin/env python3 defers the choice to a PATH lookup.
solid answer
~40 sThe shebang is consulted only when the file itself is executed — the exec bit set and the path run directly. The kernel reads the first line, treats the text after `#!` as an absolute path to an interpreter plus at most one argument, and re-executes with the script path appended. `#!/usr/bin/python3` therefore hard-pins a specific interpreter, while `#!/usr/bin/env python3` runs `env`, which searches `PATH` for `python3` — the indirection that makes a script follow whatever environment is currently first on `PATH`. Running the same file as `python3 script.py` bypasses the shebang entirely, because you have already chosen the interpreter. Installed console scripts get an absolute shebang pointing at the interpreter that installed them, which is why calling an environment's script by full path works without activating anything.
code
console · 4 lines$ printf '#!/usr/bin/env python3\nimport sys\nprint(sys.executable)\n' > show.py
$ chmod +x show.py
$ ./show.py
$ python3 show.pygo deeper
Recall what #! does at all: it tells the system which program should run this file when you execute the file itself. Know that #!/usr/bin/env python3 looks the interpreter up on PATH and that the file needs its execute bit set.
Explain the mechanics precisely: the kernel reads the first line, requires an absolute interpreter path, appends the script path, and is bypassed entirely when you invoke the interpreter yourself. Contrast the pinned and env forms and say what each buys.
Bring the operational consequences: installed commands carry absolute shebangs and break when an environment is relocated, kernel line-length limits break deeply nested environments, and a deployed script that follows the caller's PATH is a reproducibility hazard.
Own the convention across a fleet: decide whether deployed entry points pin an interpreter path or follow an environment, and make that consistent in images and job definitions so no script's behaviour depends on how an operator happened to invoke it.
## What the kernel actually does A shebang is not a Python feature; it is an operating-system convention. When you execute a file directly — `./annotate.py`, or by a bare name found on `PATH` — the kernel inspects the first bytes. If they are `#!`, the kernel treats the rest of that first line as the real program to run: an absolute path to an interpreter, optionally followed by arguments. It then executes that interpreter, passing the path of the original file as an argument. From Python's point of view nothing special happened: it was simply started with a script path. The details that bite are all in that sentence. The interpreter path must be **absolute**; the kernel does not search `PATH` for it. Argument handling is limited and not portable — traditionally everything after the interpreter path is delivered as a *single* argument, so `#!/usr/bin/python3 -X dev` may or may not do what you expect depending on the system. And the line is length-limited by the kernel; historically Linux truncated it at well under 200 bytes, which is exactly why an environment created under a deeply nested directory can produce console scripts that fail with a confusing “bad interpreter” error. Finally, the file needs the execute permission bit — without it, the shebang is just a comment, since `#` starts a comment in Python. ## The two idioms, and what each one buys `#!/usr/bin/python3` names one interpreter and will keep naming it forever. That is a feature for a system utility that must run against the interpreter the operating system maintains, and a bug for anything meant to follow a project environment. `#!/usr/bin/env python3` runs the `env` program — which *does* live at an absolute path on essentially every Unix — and asks it to find `python3` on `PATH` and exec it. The result is a script that follows whichever environment is first on `PATH` at the moment it runs. That is usually what you want during development and usually what you do **not** want for something deployed, because it makes the script's behaviour depend on the caller's environment. Note what neither idiom does: nothing here consults an environment file, a project configuration or a version manager. It is a path lookup and nothing more. ## When the shebang is ignored The shebang matters only when the *file* is executed. Every one of these bypasses it entirely: `python3 script.py`, `python3 -m package`, importing the module from another program, or executing the file through a language-level API. Which means a script can behave differently depending on how it is started — correct under `./script.py` because the shebang finds the right interpreter, broken under `python script.py` because you handed it to whatever `python` resolves to. When you want determinism, name the interpreter explicitly rather than relying on either mechanism. ## Why installed command-line tools have absolute shebangs When a package that publishes a command-line entry point is installed, the installer generates a launcher script and writes a shebang naming **the interpreter it is being installed for**, by absolute path. That is a deliberate choice: it makes the tool self-contained. Calling `<env>/bin/<tool>` by its full path works with no activation and no `PATH` manipulation, because the kernel reads that absolute shebang and starts the right interpreter, which then finds the right `site-packages`. The same mechanism explains why moving or renaming an environment's directory breaks its installed commands while leaving the interpreter itself perfectly usable: the shebangs still point at the old absolute path. ## Portability edges worth knowing Windows has no kernel-level shebang handling at all; a `.py` file there is dispatched by file association or by a launcher that reads the shebang *as text*. Some environments containing spaces in a path break shebang parsing. And a script that must pass interpreter options is better served by a two-line trick — setting the options inside the script, or wrapping it — than by piling arguments onto a shebang whose argument splitting is not guaranteed. The practical summary: use `env python3` for scripts meant to follow a developer's environment, absolute paths for scripts that must not, and never assume a caller ran your file the way you intended.
- Why would you choose `#!/usr/bin/python3` over `#!/usr/bin/env python3`?Because you want the script pinned to one known interpreter regardless of the caller's environment — typically a system utility that must run against the interpreter the operating system maintains, or a deployed script whose dependency set lives in exactly one place. The `env` form deliberately gives up that guarantee in exchange for following whatever is first on `PATH`, which is convenient in development and unpredictable in production.
- Why do an environment's installed commands stop working after you move that environment's directory?The installer wrote each command's shebang as an absolute path to the interpreter inside that directory. Move or rename the directory and the shebang points at a path that no longer exists, so the kernel refuses to start it — usually as a “bad interpreter” error — even though the interpreter itself works fine at its new location. Recreating the environment, or reinstalling into it, regenerates the shebangs.
saying these in an interview costs you the question
- Thinks the shebang applies when running `python script.py`
- Believes the kernel searches PATH for the shebang path
- Says `#!/usr/bin/env python3` pins a specific version
- Forgets the file needs the execute permission bit
- Assumes shebang arguments split like a shell command line
- Expects Windows to honour shebangs at the OS level