Skip to content

Instantly share code, notes, and snippets.

@avih
Created December 3, 2024 15:48
Show Gist options
  • Save avih/6752ad1e20b334b56fef120cd09c766e to your computer and use it in GitHub Desktop.
Save avih/6752ad1e20b334b56fef120cd09c766e to your computer and use it in GitHub Desktop.
Shell quoting 101
avih's POSIX shell quoting primer
---------------------------------
LWTS: Literal White-space, Tilde, or shell-Special (semicolon,
parenthesis, and few more). Literal is important, i.e. at
the script source text. E.g. $x containing WTS is not LWTS.
EXP (expansions): $param, ${...}, $((...)), $(...) (avoid `...`).
IFS (variable): Can only (maybe) split direct result of EXP.
GLOB (pattern): has * or ? or [ and ] (same syntax as PATTERN).
Can only (maybe) [further] split result of above.
- [sub-]WORDs without LWTS, EXP, GLOB don't need quotes.
E.g. echo, yes, no, x86, /x/y, x-y, x.y, x:y, @, = .
- LWTS need quotes to preserve their literal value (tilde not always,
but use quote if not sure. $'...' is already quoted, but avoid it).
- In assignments there's _no_ IFS-split or glob-expansion, so EXP
don't need quotes, even with white space inside EXP.
- Between 'case' and 'in' there is _no_ IFS/glob (like assignment).
- In commands and arguments there _is_ IFS-split/glob-expansion, so
quotes can prevent those (literal tilde too, as mentioned). If
we want the tilde/split/glob then don't quotes those parts. If we
_know_ EXP result is without IFS/glob chars, quotes are optional.
- Assignment-arguments, like export a="$b" had IFS/glob before
POSIX 2024. Now it doesn't, but do use quotes for portability.
Nested quotes:
- Inside command substitution $(...) it's the normal rules above
regardless of outer quotes. Safe to recurse as much as needed.
- Substring expansion like ${x#PAT} can have quotes in PAT like the
patterns in case...esac, regardless of outer quotes. However, try
to avoid the outer quotes by using an assignment, as some shells
mis-handle nested substring quotes, espesially single-quotes.
- In maybe-other-value ${x:-"$y"} or "${a+$b}" etc, there _can't_ be
both outer and inner quotes. Outer quotes make it single argument,
inner quotes make it zero or more. E.g. ${x+"$a" "$b"} is 0 or 2.
Examples:
echo hello world # no LWTS/EXP/GLOB -> no quotes
a= b=foo c=$x$y* d=$(...) # assignments, no LWTS -> no quotes
e=$(( a + b )) f=${w%"$z"* } # even with literal white-sp in EXP
g="x $y" h="(" i=";" # LWTS always need quotes
a="; \$" ; b=$a # quote LWTS in a=..., no LWTS in b=$a
case $x in ... esac # no IFS/glob between case and in
test "$x" = hello # no LWTS, only $x is EXP -> quote
"$cmd" "$(...)" foo "$x" # cmd/args: only foo is not EXP
a=$x b=$y mycmd x "$x" "$y" # only cmd/args have IFS/GLOB -> quote
for x in $values; do ... # IFS-split. but also glob! careful
for x in "$mypath"/*; do... # glob expansion in arbitrary path
export CFLAGS="$MY_FLAGS" # use quotes for portability
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment