Linux & Bash · Part I · Module 9 · Quoting and Shell Expansion
Double Quotes, Single Quotes and the Backslash
Quoting has no rules of its own. Each of the three tools switches off a specific set of stages in the pipeline, and once you know which, you can work out any case from first principles instead of trying things until one works.
Intermediate18 min readLinuxPart IFundamentalsQuotingExpansion
Part I · Module 9 · Chapter 9.3 · Double Quotes, Single Quotes and the Backslash
Chapter 9.1 said quoting switches pipeline stages off. This is the chapter that says which.
1. Three Tools
2. The Table That Answers Everything
Read it as "does this stage still happen?"
| Pipeline stage | Bare | "double" | 'single' |
|---|---|---|---|
| 1 — Brace expansion | yes | no | no |
| 2 — Tilde expansion | yes | no | no |
3 — Variables $name | yes | yes | no |
3 — Command output $(…) | yes | yes | no |
3 — Arithmetic $((…)) | yes | yes | no |
| 4 — Word splitting | yes | no | no |
| 5 — File-name matching | yes | no | no |
Three columns, and everything in this module follows from them:
- Single quotes switch off every stage. What you typed is what the command gets.
- Double quotes switch off four of the seven rows and keep the three that substitute values.
- Bare text gets all of them, which is why bare text is dangerous.
3. Seeing the Table Work
$ v=world
$ printf ' double [%s]\n' "$v and $HOME"
double [world and /home/engineer]
$ printf ' single [%s]\n' '$v and $HOME'
single [$v and $HOME]
$ printf ' backsl [%s]\n' \$v
backsl [$v]Three ways to handle the same text, three different answers — all predictable from the table.
What survives inside double quotes
$ v=X
$ echo "dollar=$v backtick=`echo B` arith=$((1+1)) cmdsub=$(echo C)"
dollar=X backtick=B arith=2 cmdsub=CAll four substituting forms fired. Note the backtick: it is still active inside double quotes, which is one more reason Chapter 8.4 said never to write it — a stray backtick in a quoted string runs a command.
What does not
$ echo "glob: *.txt brace: {1..3} tilde: ~"
glob: *.txt brace: {1..3} tilde: ~None of the three expanded. Compare bare:
$ echo glob: *.txt brace: {1..3} tilde: ~
glob: a.txt b.txt two words.txt brace: 1 2 3 tilde: /home/engineer4. Single Quotes
Single quotes are the strongest and the simplest: everything between them is literal. There are no escapes, no exceptions, and no way to interpret anything.
They are the right tool whenever the text contains characters the shell would otherwise act on:
grep 'ERROR.*timeout' run.log # regex metacharacters, untouched
awk '{ print $1, $3 }' results.txt # $1 is awk's, not the shell's
echo 'Total: $500' # a literal dollar sign
sed 's/old/new/g' file # slashes and allThat awk line is the clearest case. $1 means something to awk and something else to the shell, and single quotes are how you make sure awk is the one that gets it. With double quotes the shell would substitute its own $1 first — usually empty — and awk would receive { print , }.
5. The Backslash
A backslash switches off the next single character.
$ echo \$HOME
$HOME
$ echo two\ words
two words
$ echo \*
*It is the right tool when exactly one character is the problem and quoting the whole string would be heavy-handed — particularly a single space in an otherwise ordinary word.
Inside double quotes, it is selective
This is the part that surprises people. Within double quotes, a backslash only escapes the characters that are still special there — $, `, ", \ and a newline. Before anything else, it stays as a literal backslash:
$ echo "a \$ b"
a $ b
$ echo "a \n b"
a \n bThe second one did not produce a newline. \n is not special to the shell at all — it is a convention that printf and echo -e understand, and inside double quotes the backslash simply had nothing to escape, so both characters survived.
$ printf 'a\nb\n'
a
bprintf interprets the escape; the shell does not. Mixing those up produces a surprising number of bug reports.
At the end of a line
A backslash as the very last character continues the line:
./run_sim.sh \
--seed 7 \
--output "$logdir/run.log" \
--verboseThis is purely cosmetic — the shell joins the lines before doing anything else — but it is how long commands stay readable. Nothing may follow the backslash, not even a space; a trailing space after it means it escapes the space and the line does not continue.
6. The Fourth Form: $'…'
Bash has one more, for when you genuinely need a control character:
$ printf '%s' $'a\tb'
a b
$ echo $'line1\nline2'
line1
line2$'…' is like single quotes, except that backslash escapes are interpreted — \n, \t, \\, \' and the numeric forms.
It is the clean way to get a tab into a variable:
tab=$'\t'
IFS=$'\n'That second line is a common sight: splitting on newlines only, so that spaces inside values survive. It is still a global IFS change with all the caveats from Chapter 9.2, but it is the correct spelling of the intent.
7. Choosing
A short decision procedure that covers essentially everything.
| What you have | Use |
|---|---|
| A value from a variable or command | Double quotes — "$var" |
Literal text with $, *, backticks, regex | Single quotes — 'ERROR.*' |
A format for another program (awk, sed, find) | Single quotes |
| Mostly literal, but one value must be inserted | Double quotes |
| One awkward character in an otherwise bare word | Backslash |
| A real tab or newline in a value | $'…' |
A path starting with ~ | Leave the tilde bare, or use "$HOME/…" |
And the general shape when you need both in one argument — which is most real commands:
grep 'ERROR' "$logfile"
awk -F, '{ print $2 }' "$results"
find "$dir" -name '*.log' -newer "$marker"
sed "s/VERSION/$ver/" template.txt > out.txtEach one is single-quoted where the text belongs to the other program, and double-quoted where a shell value is being inserted. That split is the habit worth building: quote by owner. If the characters are meant for awk, sed, find or grep, single-quote them so the shell keeps its hands off; if the value is the shell's, double-quote it.
The find line is worth a second look — -name '*.log' is single-quoted so that find receives the pattern and does its own matching. Unquoted, the shell would match it against files in the current directory at stage five, and find would be handed whatever happened to be there.
8. Reading What You Wrote
$ v="my dir"
$ set -x
$ ls -d $v
+ ls -d my dir
$ ls -d "$v"
+ ls -d 'my dir'
$ set +xset -x re-quotes the finished arguments for display, so you can see directly how many there are and where their boundaries fell.
For values, printf %q does the same job:
$ v="it's a \"test\" file.txt"
$ printf 'raw : %s\n' "$v"
raw : it's a "test" file.txt
$ printf '%%q : %q\n' "$v"
%q : it\'s\ a\ \"test\"\ file.txtThe %q form shows the value quoted so that pasting it back into a shell reproduces it exactly. It is ugly on purpose — it is telling you precisely which characters needed protecting.
Practice
mkdir -p /tmp/q && cd /tmp/q
touch a.txt b.txt "two words.txt"
v=world1 — Walk the table. For each row, predict then check:
echo {1..3}; echo "{1..3}"; echo '{1..3}'
echo ~; echo "~"; echo '~'
echo $v; echo "$v"; echo '$v'
echo $(date +%Y); echo "$(date +%Y)"; echo '$(date +%Y)'
echo *.txt; echo "*.txt"; echo '*.txt'2 — Prove backticks still work in double quotes.
echo "today is `date +%F`"Then convince yourself why that is a reason to avoid them.
3 — Hit the tilde trap.
echo ~/project
echo "~/project"
echo "$HOME/project"4 — Discover that a single quote cannot be escaped. Type echo 'don\'t' and press Enter. When you get the > prompt, press Ctrl-C.
5 — Watch the backslash be selective.
echo "a \$ b"
echo "a \n b"
printf 'a\nb\n'6 — Protect the other program's syntax.
printf 'a,b,c\n' > r.csv
awk -F, '{ print $2 }' r.csv
awk -F, "{ print $2 }" r.csvThe second prints an empty line, because the shell ate $2 before awk saw it.
7 — Use $'…'.
printf '%s' $'col1\tcol2'; echo
tab=$'\t'; printf 'a%sb\n' "$tab"8 — Inspect with %q.
for s in "plain" "two words" "it's" 'a"b' 'x*y'; do printf '%-12s -> %q\n' "$s" "$s"; done9 — Clean up. cd /tmp && rm -rf /tmp/q
Check Your Understanding
1. Which stages does a double quote switch off, and which does it leave running?
2. echo "~/logs" prints ~/logs rather than a path. Why, and what are two fixes?
3. Why must awk '{ print $1 }' use single quotes?
4. echo "a \n b" prints a literal backslash-n. Why, and what would produce a newline?
5. Can you put a single quote inside a single-quoted string?
6. find . -name *.log behaves differently depending on what is in the current directory. Why, and what is the fix?
9. Remember
- Quoting switches pipeline stages off. Which stages is the whole of it.
- Single quotes switch off everything. No expansions, no escapes, no exceptions.
- Double quotes keep variables, command output and arithmetic, and switch off braces, tilde, word splitting and file-name matching.
- Bare text gets every stage, which is why it is the dangerous one.
"$var"is the default because it keeps what puts a value in and stops what takes it apart.- Backticks still fire inside double quotes — one more reason not to use them.
- Quotes switch off the tilde. Leave
~bare, or use"$HOME/…"in scripts. - A single quote cannot appear inside single quotes, by any means.
- A backslash switches off the next character, and inside double quotes only before
$, backtick,",\and newline — elsewhere it stays literal. \nisprintf's escape, not the shell's.- A trailing backslash continues a line, and nothing may follow it.
$'…'gives real tabs and newlines; it is a Bash extension, andprintfis the portable alternative.- Quote by owner. Single-quote text meant for
awk,sed,findorgrep; double-quote values that belong to the shell. set -xandprintf %qshow you what you actually built.
You can now choose a quote deliberately. The next chapter deals with the case this one deferred twice: what to do when the text you need to quote already contains quotes of its own.
Where this fits
Part of the Linux & Bash curriculum.
