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: on execve("/path/script", ...) the kernel inspects the first bytes; #! triggers script handling.
  • The kernel does not do $PATH lookup for the interpreter: #!bash would be treated as ./bash, not $(which bash).
  • zsh has extra logic: when execve returns ENOEXEC or ENOENT, zsh inspects the file, parses #!, and itself resolves the interpreter via its own path lookup, which is why #!bash appears to “work” only in zsh.
  • Other exec* functions and system() in libc do perform $PATH lookup 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 bash is widely advocated as the most practically portable way to get “whatever bash is in PATH”, and works on NixOS and many nonstandard layouts.
  • #!bash is 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 bash will eventually fail somewhere; others note even this assumes /usr/bin/env exists and $PATH is sane.
  • Discussion clarifies that /bin/sh, /usr/bin/env, #! itself, and env -S are 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/env can 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 env introduce 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 -S is 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/env and Nix shebangs, given nonstandard paths.
  • Some Python users deliberately use relative shebangs into venv/bin/python3 to avoid activation, trading flexibility for explicit project-local environments.
  • BOM-prefixed UTF‑8 files break shebang parsing, causing confusing “bad interpreter” errors.