A surprise with how '#!' handles its program argument in practice
How shebangs are handled (kernel vs shell, PATH, relatives)
- Most comments reiterate that the kernel handles
#!, not the shell: onexecve("/path/script", ...)the kernel inspects the first bytes;#!triggers script handling. - The kernel does not do
$PATHlookup for the interpreter:#!bashwould be treated as./bash, not$(which bash). - zsh has extra logic: when
execvereturnsENOEXECorENOENT, zsh inspects the file, parses#!, and itself resolves the interpreter via its own path lookup, which is why#!bashappears to “work” only in zsh. - Other exec* functions and
system()in libc do perform$PATHlookup for the program itself, but that is separate from how the interpreter path on the shebang is resolved.
Portability and recommended shebang forms
#!/usr/bin/env bashis widely advocated as the most practically portable way to get “whatever bash is in PATH”, and works on NixOS and many nonstandard layouts.#!bashis rejected as non-portable and often simply broken (works only in zsh, and only in specific situations).- Some argue anything other than
#!/usr/bin/env bashwill eventually fail somewhere; others note even this assumes/usr/bin/envexists and$PATHis sane. - Discussion clarifies that
/bin/sh,/usr/bin/env,#!itself, andenv -Sare conventions, not POSIX requirements, though they are ubiquitous in practice.
Security considerations
- Several commenters see no new security issue: making a script executable already grants it arbitrary power.
- Others point out path-based risks:
#!/usr/bin/envcan hit a malicious binary earlier in$PATH; relative interpreters (e.g.#!venv/bin/python3) can behave unexpectedly if directory layout changes. - Consensus: relative interpreters and
envintroduce familiar PATH risks, but nothing fundamentally new or special to shebangs.
OS quirks, limits, and nested interpreters
- Linux supports “nested interpreters” (an interpreter that is itself a script with its own
#!); OpenBSD does not. - FreeBSD historically allowed multi-argument/oneline shebangs, later restricted;
env -Sis cited as a non-portable workaround. - There’s a 256‑byte implementation limit on shebang length.
Practical workflows and annoyances
- NixOS users lean on
#!/usr/bin/envand Nix shebangs, given nonstandard paths. - Some Python users deliberately use relative shebangs into
venv/bin/python3to avoid activation, trading flexibility for explicit project-local environments. - BOM-prefixed UTF‑8 files break shebang parsing, causing confusing “bad interpreter” errors.