Terminal · foundation
Quoting and escaping
Shell quoting is syntax the shell consumes while building command arguments. Single quotes preserve every enclosed character literally; double quotes still allow parameter, command, and arithmetic expansion while suppressing word splitting and pathname expansion; an unquoted backslash protects the next character, except that backslash-newline continues the line.
Why it matters
Correct quoting keeps data as data. It preserves empty values and whitespace, prevents wildcard characters from unexpectedly becoming filenames, and lets scripts pass one intended argument per value. Quoting bugs often stay hidden until input contains spaces, newlines, glob characters, or an empty string.
Mental model
How to reason about quoting and escaping
Think in two stages: first the shell parses and expands the command line; then it launches the program with an array of arguments. Quotes guide the first stage and are normally removed before the second. For every expansion, ask whether its result must remain exactly one argument; if it must, quote it.
Analogy
Quotation is protective packaging applied before shipping. Single quotes seal the box completely, double quotes leave controlled openings for substitutions, and quote removal discards the packaging after the shell has assembled the final argument.
Examples
See the boundary, not just the happy path
Worked example · Preserve an expanded value
report='Quarterly Reports'
printf '<%s>\n' "$report"Double quotes allow parameter expansion while preserving the expanded text as one argument. The output is one line, <Quarterly Reports>, even though the value contains a space.
Worked example · Compare single and double quotes
printf '%s\n' '$HOME' "$HOME"Single quotes pass the literal text $HOME. Double quotes allow HOME to expand while keeping the resulting path in one argument.
Useful contrast · Choose who expands the wildcard
printf '%s\n' *.log
printf '%s\n' '*.log'The first line asks the shell to replace *.log with matching pathnames. The second passes one literal pattern. Commands such as find need the quoted form because they interpret the pattern themselves.
Worked example · Include a single quote
printf '%s\n' 'Don'\''t panic'A single quote cannot appear inside single quotes. This closes the quoted region, adds an escaped literal quote, and opens a new single-quoted region.
Avoid · Lose argument boundaries
value='one two'
printf '<%s>\n' $valueThe unquoted expansion is subject to word splitting, so printf receives two data arguments and prints two lines. Quoting "$value" would preserve one argument.
Common mistakes
Misconceptions to remove early
Quoting a variable at assignment but not at use
Assignment values do not undergo the same word splitting and pathname expansion as ordinary unquoted arguments, but a later use as $value can still change argument boundaries. Quote the expansion at the point where it becomes an argument.
Trying to escape a single quote inside single quotes
In POSIX-like shell syntax, backslash has no special role inside single quotes; close the quote, add an escaped quote, then reopen it.
Expecting single and double quotes to expand alike
Single quotes suppress parameter and command expansion, while double quotes allow selected expansions. Use '$HOME' for literal notation and "$HOME" when the value should expand as one argument.
Storing shell syntax in a plain string
Quotes stored inside a variable are data, not fresh shell syntax, so args='--name "Ada Lovelace"' does not recreate the intended grouping when expanded. In Bash, use an array; in portable shell code, build positional parameters with set --.
Quoting away tilde expansion
A tilde inside quotes is literal, so "~/notes" does not expand to a home-directory path. Use "$HOME/notes" when the path must be quoted safely.
Quick check
Can you predict the result?
1. Which form expands HOME while preserving the result as one shell word?
- • "$HOME"
- • '$HOME'
- • $HOME/*
2. Does a program normally receive the quote characters used to group a shell argument?
3. Why quote '*.log' in find . -name '*.log' but leave *.log unquoted when passing matching files directly to a command?
Keep building
Related concepts
Put the concept to work