Shell, explained

What is a shebang?

#!/usr/bin/env bash

The shebang is the first line of a script, starting with #!. It names the program that should run the file. When you type ./deploy.sh, the system reads that line and starts the named program, handing it the script. #!/bin/bash names one exact file; #!/usr/bin/env bash asks env to find bash wherever it is installed.

What ./deploy.sh really starts

A Mac with a newer bash installed through Homebrew. Switch the first line and follow which bash ends up running the script.

$./deploy.sh

Tap a part of the first line

FIRST LINE
deploy.shTHE FILE
1
2echo "deploying with bash $BASH_VERSION"
The system reads line 1 and starts this instead, adding ./deploy.sh at the endSTARTED
/usr/bin/envbash./deploy.sh
env looks for bash in each PATH folder, in orderPATH LOOKUP
›/opt/homebrew/binFOUND
/usr/binNOT REACHED
/binNOT REACHED
bash opens deploy.sh and runs it. Line 1 starts with #, so to bash it is just a comment.RUNS THE SCRIPT
/opt/homebrew/bin/bash./deploy.shbash 5, from Homebrew
Tap a first line to see what the system runs

Windows line endings break it

A file saved with Windows line endings has an invisible carriage return at the end of line 1, so the system looks for a program called bash^M.

$

Tap a part of the command to find it below

$ ./deploy.shEXIT 126
bash: ./deploy.sh: /bin/bash^M: bad interpreter: No such file or directorystderr
Tap any part of the command to see what it does

Part by part

#!
The marker. It has to be the first two characters of the file, with nothing before it.
/usr/bin/env
The program to start: env, which runs another program after finding it in PATH.
bash
What env should look for. The first bash in your PATH wins.
#!/bin/bash
The direct form: always this exact file, whatever else is installed.

Common mistakes

Anything before #!

A blank line, a space or a comment above it and the line is no longer a shebang, just a comment. The script then runs in whatever shell started it.

Options after env

On Linux everything after the program is passed as one argument, so #!/usr/bin/env bash -e looks for a program named “bash -e”. Put set -e on the next line, or write #!/usr/bin/env -S bash -e.

Try it

$ head -1 deploy.sh

Show a script's first line.

$ which -a bash

Every bash in your PATH, in the order env would find them.

$ bash deploy.sh

Starting bash yourself: the shebang is read as a plain comment.

Questions

Should I use #!/bin/bash or #!/usr/bin/env bash?

#!/usr/bin/env bash is more portable: it works where bash lives somewhere else (NixOS, FreeBSD) and picks up a newer bash on macOS. #!/bin/bash is more predictable: the same interpreter no matter what the user's PATH holds, which is why system scripts prefer it.

What happens with no shebang at all?

The system will not start the file, so your shell runs it itself: bash uses bash, zsh uses sh. The script may work for you and fail for someone using a different shell.

What does #!/bin/sh change?

It asks for the plain POSIX shell. On Debian and Ubuntu sh is dash, which has no arrays and no [[ ]], so a script using bash features fails there. Use sh only if you stick to POSIX syntax.

Why is it called a shebang?

From the names of its two characters: # is a sharp or hash, and ! is a bang.

Terminaster

Stop looking up the same command twice.

Ask about any command you run into and see what it actually does. Terminaster then brings it back just before you would forget it, until you can type it without searching.

Terminaster's tutor answering “What does chmod 754 mean?” by taking the command apart: chmod, then 7 for you, 5 for your group, 4 for everyone else, and the file name