Shebang and executables
How
- Explain what the shebang line does and why /usr/bin/env python3 is preferred over a hard-coded path
- Describe why shebangs are ignored on Windows and how pip works around this
- Use [project.scripts] in pyproject.toml to let pip generate the correct launcher for each platform
A Python script is just a text file. Making it directly executable — so users
can run mytool instead of python3 mytool.py — requires different mechanisms
on Unix and Windows. pyproject.toml entry points let you declare the intent
once and let pip handle the platform-specific details.
The shebang on Unix
On Linux and macOS, the kernel reads the first two bytes of a file when it is
executed. If they are #!, the rest of the line is treated as the interpreter
path:
#!/usr/bin/env python3/usr/bin/env python3 asks the env utility to find python3 in the current
PATH. This is correct for virtual environments: if the user has activated a venv,
python3 resolves to the venv's interpreter, not the system one.
A hard-coded path like #!/usr/local/bin/python3 breaks whenever the interpreter
is installed elsewhere — which is common across Linux distributions, Homebrew on
macOS, and pyenv. Always use env.
The file also needs execute permission:
chmod +x mytool.py
./mytool.pyShebangs on Windows
The Windows kernel does not read shebang lines. Running ./mytool.py on Windows
opens the file in the associated application (usually a text editor), not the
Python interpreter. The Python Launcher for Windows (py.exe) does understand
shebangs when you run py mytool.py, but that is not the same as a bare executable
invocation.
More importantly, the shebang does nothing when the script is installed as a
package — users still have to know to type python -m mytool or python mytool.py
rather than just mytool.
Entry points: the portable solution
[project.scripts] in pyproject.toml declares a command-line entry point:
[project.scripts]
mytool = "mytool.cli:main"When pip install mytool runs:
- On Linux/macOS: pip writes a small script file to
~/.local/bin/mytool(or the venv'sbin/) with a#!/usr/bin/env python3shebang and a call tomytool.cli:main. It sets the execute bit automatically. - On Windows: pip writes
mytool.exeandmytool-script.py(plus amytool.cmdbatch file) to the Scripts directory. The.exelauncher calls the Python script correctly without needing a shebang.
The result: mytool works as a bare command on all three platforms. You write the
shebang once in the pyproject.toml declaration; pip materialises the right
launcher for the current OS.
The Windows .cmd wrapper also handles the case where Scripts/ is on the PATH
but the user is running from Command Prompt rather than PowerShell. The mytool
command works identically in both shells.
What this means in practice
Two rules cover almost everything:
- Include
#!/usr/bin/env python3at the top of any script you distribute standalone (e.g., a single-file utility checked into a repository). It costs nothing on Windows and makes the file executable on Unix. - For installable packages, declare commands in
[project.scripts]and let pip handle the launchers. Do not write your own shebang management,.batfiles, or shell wrappers — pip already does this correctly for every platform.
The pattern generalises to GUI applications ([project.gui-scripts]) and to the
plugin entry points covered in the Plugin Systems module — all use the same
underlying mechanism.
Where to go next
You have completed the Cross-Platform module. The advanced utilities track continues with the Plugin Systems module if you have not worked through it, or move to the workflow track to apply these patterns in CI/CD pipelines.