Most shell bugs are quoting bugs. Unquoted expansion splits into words and lets the globber loose; over-quoting turns your regex literal. The rules are short, and once they click, half the weirdness in your scripts disappears.
The three layers #
Bash processes a line in passes: expansion happens first, then word splitting, then globbing — and quoting removes stages, not characters:
$ echo "$var" # double: expand, then keep as ONE word
$ echo '$var' # single: no expansion at all
$ echo $var # bare: expand, split on spaces, glob each word
Double quotes expand variables but protect the result from splitting. Single quotes protect everything. Bare quotes are a lottery played with your filenames.
The demonstration that ends debates #
$ var="my notes.txt"
$ cp $var backup/ # copies "my" AND "notes.txt" — wrong
$ cp "$var" backup/ # copies "my notes.txt" — right
$ cp '$var' backup/ # copies a file literally named $var — no
One line of defense covers almost everything: quote every expansion — "$var", "${arr[@]}", "$(cmd)".
Single vs double, precisely #
| You write | Variables expand | Globs expand | Use when |
|---|---|---|---|
"$var" | Yes | No | Default choice |
'$var' | No | No | Literal text, regexes, sed patterns |
$var | Yes | Yes | Almost never |
`cmd` / $(cmd) | Yes (in double) | No (quoted) | Command substitution |
Command substitution follows the same law — cp "$(find ...)" backup/ is as wrong as the bare variable, because the result still splits. The quotes belong outside the $(...):
$ echo "today is $(date +%A)"
$ grep "$(printf '%s' "$pattern")" file
The escaping layer #
Inside double quotes, backslash still escapes "$", `, ", and \. Inside single quotes, nothing escapes — the standard trick for one literal quote is ending and restarting the string: 'don'\''t'.
Where quoting meets arrays ("${arr[@]}" — the one true form), see arrays. The scripting series continues from here.
