Section 1: Foundations of reliable scripts
Quoting: the rule that prevents most bugs
When Bash expands an unquoted variable, it does two extra things: word splitting (breaking the value on spaces, tabs and newlines) and globbing (treating *, ? and [ as file name patterns). Both are almost never what you want.
The classic disaster
dir="/srv/My Files"
rm -rf $dir/cache # unquoted!
Bash splits the value, and the command becomes rm -rf /srv/My Files/cache, which tries to remove /srv/My and Files/cache. With different values this pattern has deleted far more than intended in real incidents.
rm -rf -- "$dir/cache"
Quoted, the path is passed as one argument, exactly as written.
Rules of thumb
- Quote every expansion:
"$var","$(command)","${array[@]}". - Use single quotes for literal text with no expansions:
'Total: $5'. - Use double quotes when you want variables expanded inside text:
"Hello, $name". - The few places where you may omit quotes, such as the right side of a plain assignment or inside
[[ ]], are not worth memorising. Quoting there is harmless.
"$@" versus $*
"$@" expands to all arguments, each as a separate word, preserving spaces inside arguments. It is what you want almost every time you pass arguments along:
run_tool() {
some-tool --verbose "$@"
}
run_tool "file one.txt" "file two.txt"
$* and unquoted $@ re-split the arguments and break names with spaces.
Command substitution
count="$(wc -l < "$file")"
Use $(...) rather than backticks: it nests cleanly and is easier to read. Quote the substitution when you use it.
Looping over files safely
for f in ./*.log; do
[[ -e "$f" ]] || continue # no matches: the pattern stays literal
gzip -- "$f"
done
Never loop over the output of ls. For recursive searches, use find with -print0 and read -d '', or find -exec, so any file name, even with newlines, is handled correctly:
find /var/log/app -name '*.log' -mtime +7 -print0 |
while IFS= read -r -d '' f; do
gzip -- "$f"
done
Check yourself
Run ShellCheck on an old script you have written. Warning SC2086, "Double quote to prevent globbing and word splitting", is the most common finding in real code. Fixing every instance is a quick win.