Variables and quoting

Updated 2026-07-05

This is the single biggest source of bash bugs, and the fix is one habit: quote your variables.

Setting and using a variable

name="deb1"
echo "Hello, $name"

No spaces around =. name = "deb1" is a syntax error, because bash parses it as running a command called name with arguments = and "deb1".

Why unquoted variables break

file="my backup.tar.gz"
rm $file
rm: cannot remove 'my': No such file or directory
rm: cannot remove 'backup.tar.gz': No such file or directory

Without quotes, bash performs word splitting on the variable's value before passing it to rm, so rm $file actually runs rm my backup.tar.gz: two arguments, neither of which is a file that exists. The file is untouched, but only by luck; if a file named my or backup.tar.gz had existed, this would have deleted the wrong thing.

rm "$file"

Quoted, $file expands to a single argument, spaces and all, and the actual file gets removed.

Word splitting and globbing are two separate dangers

pattern="*.txt"
echo $pattern
echo "$pattern"
a.txt b.txt
*.txt

Unquoted, $pattern isn't just word-split, it's also handed to the shell's filename expansion (globbing), so *.txt turns into whatever files happen to match in the current directory. Quoted, it stays the literal string *.txt. This is a second, independent reason an unquoted variable can do something you didn't intend, on top of word splitting.

Double quotes vs single quotes

name="deb1"
echo "Hello, $name"
echo 'Hello, $name'
Hello, deb1
Hello, $name

Double quotes allow variable and command expansion inside them; single quotes suppress it entirely, treating everything between them as literal text. Use single quotes for text you want passed through unexpanded, commonly an awk or sed script handed to those commands as an argument, where $1 or $name should mean something to awk, not to bash.

Arrays need quoting too, and for the same reason

arr=("one two" "three")
for x in "${arr[@]}"; do echo "item: $x"; done
for x in ${arr[@]}; do echo "unquoted: $x"; done
item: one two
item: three
unquoted: one
unquoted: two
unquoted: three

"${arr[@]}" (quoted) expands to each array element as its own word, spaces and all. ${arr[@]} (unquoted) re-splits every element on whitespace first, turning two elements into three words. The same rule from the top of this lesson applies here without exception: quote the expansion.

Giving a variable a default value

echo "${myvar:-default value}"

${var:-default} expands to $var if it's set and non-empty, or the literal default otherwise, without changing $var itself. It is a common way to write a script argument or environment variable that works whether or not the caller provided one.

Exercises

  1. Given dir="Project Files", write a command that safely creates that directory, handling the space correctly.

    Answer
    dir="Project Files"
    mkdir "$dir"

    Without the quotes, mkdir $dir would try to create two directories, Project and Files.

  2. What does echo 'Total: $((2 + 2))' print, and why doesn't it show 4?

    Answer

    It prints the literal text Total: $((2 + 2)). Single quotes suppress all expansion, including arithmetic expansion, not just variable expansion. Switching to double quotes, echo "Total: $((2 + 2))", would print Total: 4.

  3. Given an array files=("report.pdf" "notes with spaces.txt"), write a loop that prints each filename on its own line, correctly handling the one with spaces.

    Answer
    files=("report.pdf" "notes with spaces.txt")
    for f in "${files[@]}"; do
      echo "$f"
    done

    Both the array expansion ("${files[@]}") and the loop variable ("$f") need quotes; missing either one reintroduces word splitting.

What's next

The next lesson covers [ vs [[ and how bash actually evaluates conditions.