1. Home
  2. Guides
  3. Quoting and Escaping in Shell Commands

Why unquoted variables break, and what to do instead

Shell quoting is not a style preference. An unquoted variable is a word-splitting instruction, and a filename with a space in it is enough to turn a working script into a broken one or an exploitable one.

By Enrique M. · 9 min read · Last reviewed 2026-10-18

The default behaviour is to split

An unquoted variable is expanded and then split on whitespace, and each resulting piece is treated as a separate argument. A filename with a space, a glob character or a newline in it therefore does not survive.

The same expansion also expands globs, so a variable containing `*` turns into a list of files, and a variable containing `~` expands to a home directory. Neither is usually what the author intended.

file="my report.txt"
cat $file        # cat: 'my' 'report.txt' — two arguments
cat "$file"      # cat: my report.txt — one argument, correct

Single quotes, double quotes, and the difference

Single quotes protect everything: no expansion of variables, no command substitution, no globbing, no backslash escapes except a literal quote. The contents are completely literal.

Double quotes protect the *structure* — the value stays one argument — but the content is still expanded: variables, command substitution and backslash escapes are all processed inside them.

That difference is the one that bites. A value that contains a dollar sign is safe in single quotes and dangerous in double quotes, because in double quotes the shell will try to expand whatever follows the dollar sign.

# Literal, always safe
name='$HOME and `whoami`'
echo "$name"     # $HOME and `whoami`

# Expanded, not what you wanted
name='$HOME'
echo "$name"     # /root

Command substitution runs code

Backticks and `$(...)` run the command inside them and substitute the output. In a double-quoted string the substitution still happens, which means untrusted data can end up being executed.

This is the mechanism behind a whole family of injection bugs. A parameter that contains `$(rm -rf ...)` and is interpolated into a command inside double quotes will be executed.

input='$(touch /tmp/pwned)'
eval "echo $input"     # creates the file
echo "$input"          # safe, prints the literal text

Passing a value to another command safely

The correct way to hand a value to another program is as a separate argument, quoted, with the command called directly. That is the shape that avoids the shell entirely and is immune to every problem above.

If you truly must build a command as a string, the safe way is to use `printf %q`, which quotes a value so that the shell will read it as exactly one argument, or to stop building strings and use an array.

# Correct: separate argument, quoted
rm -- "$file"

# Correct: an array, no re-parsing
cmd=(rm -- "$file")
"${cmd[@]}"

# Correct: a format string, never interpolation
printf 'Processing %s\n' "$file"

Things that are not quoting

A backslash before a space works in an interactive shell, but it is fragile, unreadable and does not survive being put in a variable. It also does nothing inside single quotes, and inside double quotes it only escapes a small set of characters.

Braces, `${var}` and `$var` are not quoting. Brace expansion happens before any other expansion and turns `{a,b}` into two words. `${var}` and `$var` are the same expansion, and both of them split and glob the result unless they are inside double quotes.

  • `\ ` escapes a space, but not inside single quotes, and not reliably inside double quotes.
  • `{a,b}` expands to two words, before variables are even looked at.
  • `${var}` and `$var` behave identically. Neither of them quotes.

What to take away

  • Always double-quote a variable expansion unless you specifically want splitting and globbing.
  • Single quotes are fully literal; double quotes still expand variables and command substitutions.
  • Never build a command by interpolating untrusted data into a string.
  • Pass values as separate arguments, and use an array or `printf %q` when a string is unavoidable.

Related tools