The two characters that tell the kernel what to do
When you run ./script.sh, the kernel reads the first two bytes. If they're #! (the shebang), the rest of the first line is the path to an interpreter. The kernel runs that interpreter with your script as the argument.
The portable shape
#!/usr/bin/env bash
#!/usr/bin/env zsh
#!/usr/bin/env python3
#!/bin/sh/usr/bin/env looks the binary up on PATH, so your script works on any box that has bash somewhere in PATH — even when Apple's bash is at /bin/bash and Linux's is /usr/bin/bash. Hard-coding /bin/bash works on macOS but breaks on minimal Linux containers.
Make it executable
chmod +x script.sh
./script.shWithout chmod +x, the kernel refuses to launch. You can still run it as bash script.sh — that bypasses the shebang and uses bash explicitly.
Don't lie about the interpreter
Shebang says one thing, body uses another? Bugs. If the body uses [[ ]] (bash/zsh-only) but shebang is #!/bin/sh, POSIX sh might choke. Pick the interpreter that matches the body and stick to it.
Flexibility and reproducibility pull in opposite directions
/usr/bin/env bash follows PATH, which is convenient for developer-installed Bash versions. A deployment that requires one verified interpreter may be safer with a controlled absolute path.
The caller can override the shebang
Running bash script.sh asks that Bash to read the file directly. Executable permission and the shebang matter when the file itself is invoked.
The first line is the contract, not the extension
A .sh suffix does not select syntax. When the file is executed directly, the shebang identifies the interpreter; when a caller runs bash file, that caller overrides it.