Shell, explained

Why quote your variables: rm $f vs rm "$f"

rm "$f"

Without quotes, bash splits a variable's value on spaces (and expands any * in it) before the command runs. With f="old notes.txt", rm $f gets two arguments, old and notes.txt, and deletes notes.txt: a different file. rm "$f" gets the one name you meant.

rm $f: one name becomes two

Tap $f to follow it. In bash this printed rm: old: No such file or directory, and deleted notes.txt.

$

Tap a word to follow it

SETf="old notes.txt"
FOLDERnotes.txtoldnotes.txttodo.txt
$Variables $name becomes its value2 words
rmoldnotes.txt
$f→oldnotes.txt
a bWord splitting3 words
rmoldnotes.txt
1 word→2 words

$f was not in quotes, so the space in its value splits it into two words.

rm receives 2 arguments

1old2notes.txt
Tap any part of the command to see what it does

rm "$f": one name stays one name

The quotes keep the value together, then disappear before rm runs.

$

Tap a word to follow it

SETf="old notes.txt"
FOLDERnotes.txtoldnotes.txttodo.txt
$Variables $name becomes its value2 words
rm"oldnotes.txt"
"$f"→"oldnotes.txt"
""Quote removal the quotes themselves disappear2 words
rmoldnotes.txt
"oldnotes.txt"→oldnotes.txt

rm receives 1 argument

1oldnotes.txt
Tap any part of the command to see what it does

Part by part

rm
Deletes each file it is given as an argument.
$f
Replaced by the variable's value, then split on spaces and expanded if it contains * or ?.
"$f"
Replaced by the value, kept as exactly one argument.

Common mistakes

A * hiding in a variable

With f="*.txt", rm $f deletes every .txt file: the * is expanded after the variable is. rm "$f" looks for a file literally named *.txt.

$@ instead of "$@" in scripts

To pass a script's arguments on unchanged, write "$@". Unquoted, every argument with a space gets split again.

Try it

$ f="old notes.txt"; printf "[%s]\n" $f

Prints two bracketed words.

$ f="old notes.txt"; printf "[%s]\n" "$f"

Prints one.

$ echo "$HOME"

Double quotes still expand variables; single quotes do not.

Questions

Does zsh do the same?

No: zsh does not split unquoted variables by default, so rm $f works there. But scripts usually run in bash or sh, which do split, so quote anyway.

What is the difference between single and double quotes?

Inside "double quotes", $variables and $(commands) are still expanded, but the result stays one word. Inside 'single quotes', nothing is expanded at all.

When is it safe to leave a variable unquoted?

Almost never by accident. The rare exception is when you want splitting on purpose, such as a list of flags, and in that case an array is usually the better tool.

Terminaster

Ask about any command and get a picture like this one.

The Terminaster tutor draws these visuals for your own questions, then turns what you learned into spaced-repetition cards so it sticks.