Globbing and Wildcards: Pattern Matching Files in Linux
What You'll Learn
- That globbing (wildcards) is a job done by the shell, not the command
- How to use the three basic symbols
*,?, and[] - How brace expansion
{}is different - How to avoid traps with
extglob, dotfiles, and no-match behavior
Quick Summary
- Any characters, any count →
*(e.g.*.txt) - Exactly one character →
?(e.g.file?.log) - One char from a set →
[](e.g.img[0-9].png) - The shell expands the pattern, not the command
1. What Is Globbing?
Conclusion: The shell expands wildcards into filenames before the command runs.
ls *.txt, only the .txt files show up. What does that * actually mean?* is a wildcard that means "any character, any number of times." And here's the key point: it's the shell, not ls, that processes the *.ls?*.txt, rewrites it into the filenames that actually exist, and then hands those to ls. We call this glob expansion.How expansion works
If the current directory has a.txt, b.txt, and memo.md:
You type: ls *.txt Shell expands: ls a.txt b.txt <- memo.md is excluded ls receives: a.txt b.txt
ls knows nothing about *. It just receives the expanded filenames.
Globbing matches filenames. It is a different mechanism from regular expressions (like .* in grep), which search inside text. Don't mix them up.
2. The * Asterisk: Any String
Conclusion: The
*matches zero or more characters, but never a leading dot.
$ ls a.txt b.txt data.csv report.txt notes.md
# Only files ending in .txt $ ls *.txt
a.txt b.txt report.txt
*.txt means "something + .txt"?* matches zero characters too, so * on its own means "everything."re*?report.txt matches. You can put * at the start, end, or middle.# Starts with re $ ls re* report.txt # Contains data (anything before or after is fine) $ ls *data* data.csv
* does not match a leading dot (hidden files). Running ls * will not show files like .bashrc. This protects you from accidentally sweeping up dotfiles (for example, with rm *).
3. The ? Question Mark: Any Single Character
Conclusion: The
?matches exactly one character, for fixed-length names.
$ ls log1.txt log2.txt log3.txt log10.txt
# log + one char + .txt (log10.txt is excluded: two chars) $ ls log?.txt
log1.txt log2.txt log3.txt
? is just one character. Why did log10.txt drop out?log?.txt means "log, then exactly one character, then .txt." log10.txt has two characters (1 and 0), so it doesn't match.??. So log??.txt matches log10.txt.# Two characters $ ls log??.txt log10.txt
4. The [] Brackets: One Char From a Set
Conclusion: The
[]matches one character from the set, with ranges and negation.
$ ls img0.png img1.png img2.png img9.png imgA.png
4-1. List the candidates
# 0, 1, or 2 $ ls img[012].png
img0.png img1.png img2.png
4-2. Use a range
# One digit from 0-9 $ ls img[0-9].png
img0.png img1.png img2.png img9.png
[0-9] means "0 through 9." Handy! Can I do letters too?[a-z] for lowercase, [A-Z] for uppercase, and you can combine them like [a-zA-Z0-9]. Just remember it always matches exactly one character.4-3. Negate it (! or ^)
# One character that is NOT a digit $ ls img[!0-9].png
imgA.png
[] syntax reference
| Syntax | Meaning |
|---|---|
[abc] |
One of a / b / c |
[a-z] |
One char a-z (range) |
[0-9] |
One char 0-9 (range) |
[!0-9] |
One non-digit char (negation) |
[a-zA-Z] |
One letter (combined ranges) |
5. Brace Expansion {}: A Separate Mechanism
Conclusion: Brace expansion builds strings whether or not the files exist.
# Brace expansion: generates three strings
$ echo file{1,2,3}.txtfile1.txt file2.txt file3.txt
{} just builds strings and does not check whether the files exist. So it produces names even for files that aren't there.{} as "create these in one go," and * or [] as "pick from what already exists."# Create numbered directories at once (they don't need to exist)
$ mkdir log{2024,2025,2026}
# A numeric range works too
$ echo {1..5}
1 2 3 4 5When * or [] match no files, the pattern is left as a literal string (bash default). Brace expansion {}, however, is always expanded. The behavior differs, so be careful.
6. extglob: Extended Globbing for More Power
Conclusion: extglob enables advanced patterns; it is off by default.
# Enable extended globbing (bash) $ shopt -s extglob
@(...) for "any of" and !(...) for "except."extglob syntax
| Syntax | Meaning |
|---|---|
?(pattern) |
Zero or one occurrence |
*(pattern) |
Zero or more occurrences |
+(pattern) |
One or more occurrences |
@(pattern) |
Exactly one (any of) |
!(pattern) |
Anything except pattern |
Separate alternatives inside pattern with |.
# Match .jpg or .png $ ls *.@(jpg|png) # Everything except .txt $ ls !(*.txt)
shopt -s extglob applies only to that shell. To use it always, add it to your ~/.bashrc. To turn it off, run shopt -u extglob.
7. Common Beginner Traps
Conclusion: No-match behavior, hidden files, and quoting are the classic traps.
7-1. No match leaves the pattern as-is
# When there are no .xml files $ ls *.xml ls: cannot access '*.xml': No such file or directory
*.xml? That's a weird error.ls as a literal string. That's why it says no such file. Use shopt -s nullglob to expand to nothing instead when there are zero matches.7-2. * does not catch hidden files
# To include hidden files $ shopt -s dotglob $ ls *
With dotglob enabled, * also matches dotfiles. Keeping it off is safer for everyday use.
7-3. Quoting disables globbing
# Quoting prevents * from expanding $ ls "*.txt" ls: cannot access '*.txt': No such file or directory
Wrapping a pattern in quotes like "*.txt" or '*.txt' stops glob expansion. That's useful when you want * as a literal character, but if you meant to expand it, quoting silently disables it.
8. Mini Exercises: Try It Yourself
Conclusion: Three tasks on extensions, length, and ranges to practice globbing.
# Prepare practice files $ touch a.txt b.txt c.log data1.csv data2.csv data10.csv
Task 1: List only the files ending in .csv.
Show hint
Express "something + .csv" with *.
Sample answer
$ ls *.csv
This shows data1.csv, data2.csv, and data10.csv.
Task 2: List data + one char + .csv only (excluding data10.csv).
Show hint
The symbol for one character is ?.
Sample answer
$ ls data?.csv
Only data1.csv and data2.csv remain. data10.csv is excluded because it has two characters.
Task 3: Show only data1.csv and data2.csv using brackets.
Show hint
Put 1 and 2 as candidates inside [].
Sample answer
$ ls data[12].csv
[12] means "one character, either 1 or 2." data10.csv is excluded.