What it is
When the kernel gets a request to execute a file that has the +x bit, it
reads the first two bytes. If they are #!, called the shebang (or
hashbang), the rest of the line is read as a path to the interpreter.
#!/usr/bin/env bash
echo hello
When you run ./script.sh, the kernel sees #! → starts
/usr/bin/env bash ./script.sh → env finds bash in $PATH →
bash interprets the script.
Without #!, the behavior depends on who runs it:
- From a bash shell, bash runs it as a bash script
- From dash/zsh/fish, it may run under the rules of that shell
- Through
exec()with no shell, the kernel refuses with ENOEXEC
So always write a shebang.
/bin/bash vs /usr/bin/env bash
Two forms you see often:
#!/bin/bash # hardcoded path
#!/usr/bin/env bash # look up bash through PATH
Prefer the second one. Here is why:
| Case | /bin/bash | /usr/bin/env bash |
|---|---|---|
macOS, bash 5 via brew in /usr/local/bin/bash | old 3.x | fresh 5.x |
Alpine Linux, bash in /usr/bin/bash or absent | does not work | works if installed |
NixOS, bash in /nix/store/.../bin/bash | does not work | works |
| Plain Debian/Ubuntu | works | works |
The exception is #!/bin/sh. POSIX guarantees that a POSIX shell is
always at /bin/sh. If your script is genuinely POSIX-compatible,
write /bin/sh and save yourself the trouble.
Arguments to the interpreter
A shebang can pass one argument (a Linux limit):
#!/usr/bin/env -S bash -euo pipefail
▸-S splits the line into tokens (without -S the whole "bash -euo pipefail" goes through as ONE argument)
With env -S (needs coreutils 8.30+) you can wedge strict mode straight
into the shebang. The alternative is set -euo pipefail as the first line
of the script.
Not only bash
A shebang works with any interpreter:
#!/usr/bin/env python3
print("hello from python")#!/usr/bin/env ruby
puts "hi"
#!/usr/bin/env node
console.log('hi')The /usr/bin/env <interpreter> idiom is universal. It works for all of them.
Common mistakes
- CRLF (Windows line endings): the kernel sees
#!/usr/bin/env bash\rand tries to find an interpreter calledbash\r. The error isbad interpreter: No such file or directory. Fix:dos2unix file.sh. - No
+xbit: runchmod +x file.sh. - No full path:
#!bashdoes not work, you need an absolute path. - Shebang not on the FIRST line: the kernel looks only at the first two bytes, so a blank line before the shebang breaks the magic.