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:

What each quoting style does
$ 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 #

A file with a space, three ways
$ 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 writeVariables expandGlobs expandUse when
"$var"YesNoDefault choice
'$var'NoNoLiteral text, regexes, sed patterns
$varYesYesAlmost 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 $(...):

Quoting a substitution correctly
$ 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.