In what order does bash apply its expansions to a command line, and why is the output of one expansion never re-scanned as a fresh expansion?
answer
- single left-to-right pass
- braces first, quote removal last
- substitution before splitting and globbing
- values are data, never syntax
- eval is the only second pass
basics
~20 sBash expands in one pass: brace expansion, tilde expansion, then parameter, arithmetic and command substitution, then word splitting, then pathname expansion, then quote removal. Each stage sees only the remaining stages, so a value that looks like shell syntax is data, not code.
solid answer
~50 sBash makes a single left-to-right pass in a fixed sequence: brace expansion first, then tilde expansion, then parameter and variable expansion, arithmetic expansion and command substitution together, then word splitting, then pathname expansion, and finally quote removal. The order is the whole explanation for several results that otherwise look arbitrary — braces cannot see a variable because braces run first, and a variable's value is subject to splitting and globbing because those stages come after substitution. The crucial asymmetry is that the shell does not restart the pipeline on what a stage produced: text that appears in a variable's value is never treated as shell syntax. So `a='$b'; echo $a` prints `$b`, and an expanded value containing a semicolon or a backtick is passed along as characters, not executed. Getting a genuine second pass requires `eval`.
code
bash · 15 linesb=hi
a='$b'
echo $a
# $b <- no second parameter expansion
eval echo "$a"
# hi <- eval re-parses, giving a second pass
c='ls; rm -rf /tmp/x'
$c
# bash: ls;: command not found <- the ';' is data, not syntax
p='*'
echo "$p" # *
echo $p # the filenames: pathname expansion runs after substitutiongo deeper
Be able to name the stages roughly in order and say the one-sentence consequence: braces come first so they cannot use variables, and quotes are stripped last.
Explain the pipeline precisely and derive results from it — why an unquoted expansion can split and glob, and why a='$b'; echo $a prints $b rather than the value of b.
Use the order as a review tool: state the guarantee that expanded data never becomes shell syntax, show the one construct that waives it, and reason about which metacharacters in a value are still live.
Own it as a security and design invariant for the codebase — data-never-becomes-syntax is what makes shell auditable, so any pattern that reintroduces a second parse needs an explicit justification and a validated input.
## One pass, seven stages After bash has parsed a command line into words and applied quoting, it performs expansions in this order: 1. **Brace expansion** — `{a,b}`, `{1..5}`; pure text generation. 2. **Tilde expansion** — `~`, `~alice`, `~+`. 3. **Parameter and variable expansion** (`$var`, `${var:-x}`), **arithmetic expansion** (`$(( ))`) and **command substitution** (`$(cmd)`), performed left to right. Process substitution, on systems that have it, happens at this point too. 4. **Word splitting** — unquoted results of stage 3 are split into words. 5. **Pathname expansion** — surviving unquoted patterns are matched against the filesystem. 6. **Quote removal** — the quotes that controlled all of the above are stripped so the command never sees them. Only one of these stages is optional in the sense that you can turn it off: pathname expansion, via `set -f`. The order itself is fixed and is not configurable. ## What the order buys you Almost every "why did bash do *that*" question resolves into a statement about position in this list. **Braces run first, so they cannot use variables.** `n=5; echo {1..$n}` prints `{1..5}` because at brace time the endpoint was still the characters `$n`. **Tilde runs before variables, for the same reason.** `echo ~$USER` gives `~alice`, not a home directory, because tilde expansion looked for a login named `$USER`. **Substitution runs before splitting and globbing, so a value is subject to both.** An unquoted `$file` whose value contains whitespace becomes several words, and one containing `*` may be replaced by filenames. That is the ordering reason quoting matters at all; the quoting rules themselves are a separate topic, but the *why* lives here. **Quote removal is last.** The quotes are still present, and still meaningful, throughout every earlier stage — that is precisely how a double-quoted `"$var"` suppresses stages 4 and 5 — and are deleted only at the very end, so the command that finally runs receives none of them. **Left-to-right within stage 3.** Substitutions in one command line are evaluated in order, which matters when one has a side effect the next depends on. ## The no-re-scan rule, stated precisely The rule is *not* "the result of an expansion is inert". It is directional: > The output of a stage still passes through the **remaining** stages of the same pass, but the pipeline never restarts, so no stage ever runs again on what a later stage produced — and nothing produced by stage 3 is ever treated as shell syntax. That second half is the one candidates get wrong. Concretely: ```bash b=hi a='$b' echo $a # prints: $b (no second parameter expansion) echo $(echo '$b') # prints: $b (command output is data too) c='ls; rm -rf /' $c # tries to run a command literally named 'ls;' ``` The last line is the safety-relevant one. The value's semicolon is not a command separator; word splitting produces the words `ls;`, `rm`, `-rf`, `/`, and bash tries to execute a program whose name is `ls;`. Data from a variable does not become syntax. Metacharacters that *are* honoured after substitution are exactly the ones belonging to the later stages: whitespace (splitting) and glob characters (pathname expansion) — never `;`, `|`, `&&`, `>` or backticks. Meanwhile the first half of the rule explains the opposite-looking behaviour: ```bash p='*' echo $p # expands to the filenames in the directory echo "$p" # prints: * ``` No contradiction: `*` was not re-parsed as syntax, it simply reached stage 5 as an unquoted pattern, and stage 5 comes after stage 3. ## When you genuinely need a second pass Because there is exactly one pass, forcing another means handing text back to the parser. That is what `eval` does — it concatenates its arguments, expands them once as a normal command line, and then executes the result as a fresh command line, which is a second full trip through parsing and all seven stages. It is the only builtin that does this, which is why the answer to "how do I make `{1..$n}` work" or "how do I run a command stored in a string with quoting intact" always circles back to `eval`. It is also why unvalidated data reaching `eval` is a shell-injection hole — the promise that data never becomes syntax is precisely what `eval` waives, and that hardening is a topic of its own. ## How to say it in an interview Recite the six stages in order, then immediately give one consequence per direction: braces cannot see variables because they run first, and a bare `$var` can split and glob because those stages run last. Finish with the no-re-scan rule and the `$c='ls; rm -rf /'` example, because that single example proves you understand both that the shell is not re-parsing your data and that word splitting still applies to it.
- If a variable's value contains a semicolon, why does bash not treat it as a command separator?Because command separators are decided during parsing, which happens before any expansion, and the pipeline is never restarted on expanded text. After substitution only the remaining stages apply — word splitting and pathname expansion — so a `;` in a value is just a character in a word. That is exactly the guarantee `eval` gives up.
- Which stage does `set -f` disable, and when would you want that?`set -f` (equivalently `set -o noglob`) disables pathname expansion, stage five. It is occasionally used around code that must pass literal patterns through to a program that does its own matching — `find -name '*.log'` style arguments built dynamically — though quoting is usually the clearer fix. Re-enable with `set +f`.
- Why is quote removal the last stage rather than something the parser does up front?Because the quotes are the control input for the earlier stages. A double quote is what tells word splitting and pathname expansion to skip that text, so it has to survive until those stages have run. Only once every expansion has consulted the quoting does bash strip the quote characters, so the executed command never sees them.
Think of it as an assembly line with six stations. A part can only move forward — a piece added at station three still visits stations four to six, but nothing ever travels back to station one, and the line never starts over unless you physically carry the part back, which is what eval does.
saying these in an interview costs you the question
- Believes bash re-expands a variable's value as code
- Puts word splitting before command substitution
- Thinks a semicolon in a value separates commands
- Says quotes are removed before expansion happens
- Claims globbing runs before variables are substituted