Globbing, Quoting, and Expansion
How the shell rewrites what you typed before a command ever sees it — pattern matching, variable substitution, and the quoting rules that control exactly when that rewriting happens.
What you'll learn
- Use glob patterns (*, ?, []) to match multiple filenames in one command
- Explain what double quotes prevent and what single quotes prevent, precisely
- Predict what a command actually receives after the shell's expansion step, before it runs
Prerequisites
Explanation
Every real command in this lesson runs only in your own terminal — the exercises below model the shell's rewriting rules as JavaScript logic and never execute anything.
Before a command ever runs, Bash performs several expansion steps on what you typed, rewriting it into the actual arguments the command receives. Globbing (filename/pathname expansion) is the most common: * matches any sequence of characters, ? matches exactly one character, and [abc]/[a-z] matches any one character from a set or range — ls *.txt doesn't pass the literal string "*.txt" to ls at all; the shell first expands it into every actual matching filename in the current directory (draft.txt final.txt notes.txt, say), and ls only ever sees that already-expanded list. If no file matches a glob pattern, Bash's default behavior is to pass the literal, unexpanded pattern string through unchanged (not an empty list) — a subtle, honestly worth-knowing detail that can produce a confusing "No such file or directory" error for a pattern that matched nothing, rather than the empty-result behavior some other shells or languages might lead you to expect.
Quoting controls exactly which expansions happen. Double quotes ("$HOME/notes") suppress globbing and word-splitting (a variable's value won't be re-split on whitespace into multiple arguments), but still allow variable expansion ($HOME inside double quotes is still replaced with its value) — this is precisely why "$var" is the standard, defensive default for referencing a variable whose value might contain spaces or glob-special characters: rm "$filename" treats $filename's entire value as one single argument, safely, even if it contains spaces. Single quotes ('$HOME/notes') suppress everything — no variable expansion, no globbing, the text between them is passed through completely literally, character for character.
The absence of quotes entirely is the case that causes the most real, painful bugs: rm $filename (no quotes at all), if $filename happens to contain a space ("my file.txt"), doesn't pass one argument "my file.txt" — the shell word-splits the expanded value on whitespace, so rm actually receives two separate arguments, my and file.txt, and may well delete or fail on files you never intended to touch at all. This single, precise mechanism — unquoted expansion undergoing word-splitting — is worth understanding exactly, not just remembering "always quote your variables" as an unexplained rule.
Example
Modeling glob expansion and the quoting rules as pure string/array logic -- no shell is invoked by this or any exercise in this course.
function expandGlob(pattern, filesInDirectory) {
// A simplified model: '*' matches any sequence of characters. Splitting the
// pattern on '*' and checking each filename starts/ends with the right
// pieces avoids needing a full glob-to-regex translator for this example.
const parts = pattern.split("*");
function matches(filename) {
if (parts.length === 1) return filename === pattern;
if (!filename.startsWith(parts[0])) return false;
if (!filename.endsWith(parts[parts.length - 1])) return false;
return true;
}
const results = filesInDirectory.filter(matches);
return results.length > 0 ? results : [pattern]; // Bash's real default: unmatched glob stays LITERAL
}
console.log(expandGlob("*.txt", ["draft.txt", "final.txt", "image.png"]));
// ["draft.txt", "final.txt"] -- the command never sees the literal "*.txt"
console.log(expandGlob("*.pdf", ["draft.txt", "final.txt"]));
// ["*.pdf"] -- NO match: Bash's default passes the literal pattern through, not an empty list
function wordSplit(value) {
// Models what happens to an UNQUOTED variable expansion: split on whitespace.
return value.split(/\s+/).filter(Boolean);
}
console.log(wordSplit("my file.txt")); // ["my", "file.txt"] -- TWO arguments, not one -- the real bug unquoted vars causeTry it yourself
Call wordSplit with a value that has no spaces at all, and confirm it correctly produces just ONE argument.
Code editor. Press Escape then Tab to leave the editor if keyboard focus becomes trapped. Press Control+Shift+M inside the editor to toggle Tab-key focus trapping.
Guided exercise
Guided exercise
This models glob expansion only -- no shell or filesystem is touched. Write expandGlob(pattern, filesInDirectory) for patterns containing exactly one '*': match any filename starting with the text before '*' and ending with the text after it. If nothing matches, return [pattern] (Bash's real default: an unmatched glob is left literal, not empty).
Checks: expands a glob pattern to every matching filename · leaves an unmatched glob pattern literal, matching Bash's real default behavior
Code editor. Press Escape then Tab to leave the editor if keyboard focus becomes trapped. Press Control+Shift+M inside the editor to toggle Tab-key focus trapping.
Stuck? Get a hint.
Independent exercise
Independent exercise
This models word-splitting only -- no shell is invoked. Write commandArgsFromUnquoted(value) modeling what an UNQUOTED variable expansion produces: split on any whitespace, filtering out empty pieces (multiple spaces shouldn't create empty arguments). Then write commandArgsFromDoubleQuoted(value) modeling a DOUBLE-QUOTED expansion: always return a single-element array [value], regardless of any spaces inside.
Checks: unquoted expansion word-splits into multiple arguments · double-quoted expansion stays as one argument regardless of spaces · a value with no spaces produces one argument either way
Code editor. Press Escape then Tab to leave the editor if keyboard focus becomes trapped. Press Control+Shift+M inside the editor to toggle Tab-key focus trapping.
Stuck? Get a hint.
Common mistakes
- Using an unquoted variable ($filename) when its value might contain a space -- the shell word-splits it into multiple separate arguments, which can cause a command to operate on files you never intended.
- Assuming an unmatched glob pattern (like *.pdf when no .pdf files exist) expands to nothing -- Bash's real default passes the literal, unexpanded pattern string through instead, which can produce a confusing 'No such file or directory' error.
- Using single quotes when variable expansion was actually needed -- single quotes suppress EVERYTHING, including $variable substitution, which is a common surprise for anyone expecting only globbing to be suppressed.
Knowledge check
Takeaway
The shell rewrites what you typed (globbing, variable expansion) before a command ever sees it — double quotes allow variable expansion while suppressing word-splitting and globbing, single quotes suppress everything, and no quotes at all risks a variable's value being silently split into multiple unintended arguments.
Summary
Globbing (*, ?, [...]) expands to matching filenames before the command runs; an unmatched pattern stays literal by default, it doesn't vanish. Double quotes allow $variable expansion but block word-splitting/globbing; single quotes block everything. Unquoted expansion word-splits on whitespace, a common, real source of bugs when a value contains spaces.
References
Your notes
Notes save automatically.
Finished this lesson?
Mark it complete to track your progress and schedule a future review.
AI tutor
The optional AI tutor isn't enabled in this deployment. All lessons, exercises, quizzes, and search work fully without it.