Variables and quoting
Where scripts break most often, and how quoting fixes it
Unquoted variables are where bash scripts break most often, but the fix is simple: 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 runs rm my backup.tar.gz, which treats my and backup.tar.gz as two
separate arguments, pointing at individual files that don't exist. 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 is word-split and then 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.
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
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.
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, and
environment variables and PATH covers how one
reaches a script in the first place.
Exercises
-
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 $dirwould try to create two directories,ProjectandFiles. -
What does
echo 'Total: $((2 + 2))'print, and why doesn't it show4?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 printTotal: 4. -
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" doneBoth the array expansion (
"${files[@]}") and the loop variable ("$f") need quotes; missing either one reintroduces word splitting.
What's next
Conditionals and test covers [ vs [[ and how bash
evaluates conditions, including what an unquoted variable does inside a test.