How Shell Expansion and Globbing Actually Work
By the time a command you typed actually runs, the shell has already rewritten it — expanding variables, substituting command output, and turning wildcard patterns into real filenames. Here's the exact order that happens in.
The command you type is rarely the command that actually runs — before execution, the shell performs a specific, ordered sequence of expansions and substitutions, rewriting your input into its final form.
Why the order of expansion actually matters
Bash performs expansions in a defined sequence: brace expansion, tilde expansion, parameter/variable expansion, command substitution, arithmetic expansion, word splitting, and finally filename (glob) expansion — in that order. Getting the mental model of this sequence right explains behavior that otherwise looks inconsistent, like why quoting a variable changes whether word splitting happens to its expanded value.
Brace expansion: pure text generation, no filesystem involved
echo file{1,2,3}.txt
# expands to: file1.txt file2.txt file3.txt
Brace expansion happens purely as text manipulation, before the shell ever checks the filesystem — file{1,2,3}.txt expands to those exact strings whether or not any of those files actually exist.
Parameter expansion: more than just $variable
echo ${name:-default} # use "default" if $name is unset or empty
echo ${name#prefix} # remove shortest matching prefix
echo ${#name} # string length
Beyond simple variable substitution, parameter expansion supports default values, prefix/suffix removal, and string length — a surprisingly capable text-manipulation toolkit that’s easy to overlook if you only ever use $variable in its simplest form.
Command substitution: running a command for its output
echo "Today is $(date +%A)"
$(...) runs the enclosed command and substitutes its standard output in place — the older backtick syntax (`date +%A`) does the same thing but nests and reads far less clearly for anything beyond a trivial case.
Word splitting: the step that quoting protects against
After expansion, the shell splits the resulting text on whitespace (or the characters in $IFS) into separate words — this is exactly the step that turns an unquoted $variable containing spaces into multiple separate arguments, rather than the one argument you likely intended.
Filename expansion (globbing): the final step, and the only one touching the filesystem
ls *.txt
ls file?.txt
ls file[0-9].txt
Globbing happens last, and it’s the only expansion step that actually checks the filesystem — * matches any sequence of characters, ? matches exactly one character, and [...] matches any single character within the specified set, all resolved against files that actually exist in the relevant directory.
Why an unmatched glob pattern doesn’t always error
echo *.xyz
# if no .xyz files exist, this prints the literal string "*.xyz"
By default, an unmatched glob pattern is passed through literally rather than causing an error or expanding to nothing — a frequent source of confusion in scripts that assume a glob will either match real files or disappear entirely, when the actual default behavior is neither.
Why understanding this sequence prevents a whole class of scripting bugs
Bugs involving “why did my variable turn into multiple arguments,” “why didn’t my wildcard match what I expected,” or “why does quoting change this command’s behavior” almost always trace back to a step in this expansion sequence behaving exactly as specified, just not as the script’s author expected — knowing the actual order (and which steps touch the filesystem versus which are pure text manipulation) turns these from mysterious bugs into predictable, fixable behavior.