Course page
Log in

You are watching a free preview. Enroll to open all 24 lessons, track progress and ask the course tutor.

Enroll for $29.99

Section 1: Foundations of reliable scripts

Quoting: the rule that prevents most bugs

Article 11 min Lesson 2 of 24 Preview

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.

← → switch lessons