skip to content

Quoting Rules

Double quotes keep expansions but stop splitting, single quotes make everything literal, and $'...' gives you real escape sequences. In interviews the answer is nearly always "quote it" — what earns the point is stating precisely which behavior each quote type suppresses.

part ofBashoverview, primer and where to startread it →
on this pageshow

questions

5

In a bash script, what is the difference between wrapping text in single quotes and wrapping it in double quotes, and which shell behaviours does each one turn off?

level: juniorimportance: must knowfreq 88%

answer

  1. one of them still expands things
  2. which stage does each quote suppress
  3. expansion kept, splitting and globbing off
  4. nothing is special inside these
  5. no escape character exists in the literal form

basics

~20 s

Double quotes still expand variables and command substitutions while suppressing word splitting and filename globbing; single quotes suppress all of that as well, so every character between them is literal, including the dollar sign and the backslash.

solid answer

~40 s

Both forms stop the shell from splitting the text into several words and from expanding globs, but they differ in what still happens inside. In double quotes, parameter expansion (`$var`, `${var}`), command substitution (`$(cmd)`) and arithmetic expansion (`$((1+1))`) all still run — what is switched off is word splitting on `IFS` and pathname expansion, which is why `"$file"` is always exactly one argument no matter what it contains. In single quotes nothing is special at all: `'Hello $name'` prints the dollar sign and the name literally, and there is no escape character, so a single quote cannot appear inside single quotes even with a backslash. A useful shorthand: double quotes mean "expand it, but keep it as one word"; single quotes mean "hands off entirely".

code

bash · 8 lines
bash
name=World
echo "Hello, $name"    # Hello, World
echo 'Hello, $name'    # Hello, $name

file='My Report.txt'
: > "$file"             # creates exactly one file
ls -1 "$file"          # My Report.txt
rm -- "$file"

go deeper

for a junior

Be ready to say plainly that double quotes still expand variables while single quotes make everything literal, and that both stop a value with spaces from becoming two arguments. Quote your expansions in any code you write on a whiteboard.

for a middle

Explain the mechanics: which expansions survive inside double quotes, that word splitting and globbing are what quoting suppresses, and that the backslash only escapes five characters inside double quotes.

for a senior

Show the production instinct — single quotes for anything another tool will parse (awk programs, regexes, find patterns), double quotes for everything else, and the ability to spot the unquoted expansion in a review before ShellCheck does.

for a principal

Frame it as a policy question: an unconditional quote-everything rule enforced by ShellCheck in CI is cheaper than teaching each engineer the exceptions, and a script that wants splitting should be using an array instead.

## What quoting is really doing Quoting in bash is not a way of "making a string" the way it is in most programming languages. Bash reads a line, then runs a series of expansions over it, and finally hands the resulting words to a command as separate arguments. Quoting marks characters so that some of those stages leave them alone. That is why the interesting question is never "does this make a string" but "which stage does this quote switch off". Two of those stages cause almost all real bugs: **word splitting**, where an unquoted expansion is chopped into several words at spaces, tabs and newlines, and **pathname expansion** (globbing), where characters like `*`, `?` and `[` are replaced by matching filenames. Both quote forms switch both of those off. The difference between them is what still runs *inside* the quotes. ## Double quotes: expansion yes, splitting and globbing no Inside `"..."` the shell still performs: - parameter expansion — `$name`, `${name}`, `${name:-default}` - command substitution — `$(date)` and the older backtick form - arithmetic expansion — `$((count + 1))` What it no longer does is split the result on `IFS` or expand `*` against the filesystem: ```bash file='My Report.txt' ls -l $file # two arguments: 'My' and 'Report.txt' -> two errors ls -l "$file" # one argument: 'My Report.txt' ``` The backslash keeps a limited special meaning inside double quotes. It escapes only `$`, a backtick, `"`, another backslash, and a newline. Before anything else it is just a literal backslash — `echo "a\tb"` prints `a\tb`, not a tab, because `\t` means nothing to the shell. Producing a real tab is `printf`'s job, or the `$'...'` form. A few characters that people expect to be neutralised are not: `"` obviously ends the quote, and history expansion with `!` still fires inside double quotes in an interactive shell (in a script, history expansion is off, so this is not a scripting concern). ## Single quotes: completely literal Inside `'...'` no character is special. There is no expansion and there is no escape character: ```bash name=World echo "Hello, $name" # Hello, World echo 'Hello, $name' # Hello, $name echo 'a\tb' # a\tb - backslash is literal too ``` The consequence people get caught by is that a single quote cannot be escaped inside single quotes. `'it\'s'` does not work; the quote simply ends at the second `'`. The standard trick is to close the quote, emit an escaped quote, and reopen: `'it'\''s'`, or just use double quotes when the text contains an apostrophe but no `$` or backtick. Single quotes are the right choice whenever you are writing text that another program will interpret: an `awk` or `sed` program, a `grep` regular expression, a `find` pattern that `find` itself should expand, a JSON body, a password with `$` in it. In all of those cases you want bash to hand the characters over untouched. ```bash grep '^\$[0-9]' prices.txt # bash passes ^\$[0-9] straight to grep find . -name '*.log' # find does the matching, not bash ``` If you had written `find . -name *.log`, bash would expand the glob first against the current directory, and `find` would receive whatever filenames happened to match — a classic source of "it worked on my machine". ## Quote removal After all expansions are done, bash strips the quote characters themselves so the command never sees them. That is why `ls -l "$file"` passes `My Report.txt` and not `"My Report.txt"` — the quotes are shell syntax, not part of the data. It also means you cannot add quotes by putting them in a variable: the contents of a variable are data, and data is not re-scanned for quoting. ## The practical rule Double-quote every expansion by default. If you find yourself wanting an expansion unquoted so it splits into several words, that is almost always a sign you want an array instead. Use single quotes for literal text that must reach another program unchanged. ShellCheck flags the unquoted case as `SC2086`, which is by a wide margin the most common finding in real shell code.

  • How do you put a literal single quote inside a single-quoted string?
    You cannot — there is no escape character inside single quotes, so the quote just ends the string. Close it, escape a quote outside, and reopen: `'it'\''s'`. If the text has no `$`, backtick or backslash, using double quotes (`"it's"`) is simpler and more readable.
  • Why does `echo "a\tb"` print a backslash and a t instead of a tab?
    Inside double quotes the backslash only escapes `$`, a backtick, `"`, `\` and a newline; before a `t` it is an ordinary character, so bash passes `a\tb` through. Interpreting `\t` is the job of `printf 'a\tb\n'`, or of the `$'a\tb'` ANSI-C quoting form. `echo -e` also does it but is not portable.
  • Does quoting change anything when the variable holds a single word with no special characters?
    Not for that value — but the quotes are protection against values you did not predict. A path that gains a space, an empty variable that would otherwise vanish and shift every later argument, or a value containing `*`. Quoting costs nothing and removes a whole class of bugs, which is why the habit is unconditional.

saying these in an interview costs you the question

  • Says single and double quotes are interchangeable in bash
  • Thinks double quotes make the shell treat the value literally
  • Believes a backslash can escape a quote inside single quotes
  • Expects \t or \n inside double quotes to become real whitespace
  • Claims quoting is only needed for values containing spaces

context

open as a page

A bash wrapper script forwards its arguments with `mytool $@`, and a user who passes an argument containing a space finds that mytool sees two arguments. What do `"$@"`, `$@`, `"$*"` and `$*` each expand to, and which belongs in that wrapper?

level: middleimportance: must knowfreq 62%

basics

~10 s

Only "$@" forwards arguments faithfully: it expands to one word per positional parameter with boundaries intact. $@ and $* are both re-split on IFS and globbed, and "$*" joins everything into a single word.

open as a page

In bash, `[[ $file == *.txt ]]` and `[[ $file == "*.txt" ]]` behave differently. What does quoting the right-hand side of `==` inside `[[ ]]` do, and where else in bash does quoting turn a pattern into a literal string?

level: middleimportance: should knowfreq 42%

basics

~20 s

Quoting the right-hand side turns a pattern into literal text. Unquoted, *.txt is a glob matched against the value; quoted, it matches only the exact four-character-plus string *.txt. The same rule applies to case patterns and to the =~ regex operand.

open as a page

A reviewer on your team insists that every expansion in a bash script be double-quoted, and a colleague objects that some of them cannot possibly break. In which bash contexts is an unquoted expansion genuinely safe, and would you still require the quotes there?

level: seniorimportance: should knowfreq 35%

basics

~20 s

Four contexts perform no word splitting or globbing: the right-hand side of a simple assignment, inside [[ ]], the word after case, and arithmetic contexts such as (( )). Everywhere else quoting is mandatory — and most teams still quote unconditionally, because the exceptions are a memory test.

open as a page

In a bash script you need one string containing a literal single quote and another containing a real tab character. Single quotes have no escape character — how do you build each of these, and why is `echo -e` not the answer?

level: middleimportance: nice to knowfreq 30%

basics

~20 s

Close, escape, reopen: 'it'\''s' produces it's because single quotes cannot contain an escaped quote. For control characters use ANSI-C quoting, $'\t', which bash converts to a real tab before the command ever runs; printf is the portable alternative to echo -e.

open as a page