Globbing and Wildcards: Pattern Matching Files in Linux

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.

Lina: Senpai, when I type ls *.txt, only the .txt files show up. What does that * actually mean?
Linny-senpai: Great question. * 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 *.
Lina: Wait, not ls?
Linny-senpai: Right. The moment you press Enter, the shell looks at *.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
Lina: So *.txt means "something + .txt"?
Linny-senpai: Exactly. And remember * matches zero characters too, so * on its own means "everything."
Lina: Then what about re*?
Linny-senpai: That means "starts with re," so 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
Lina: So ? is just one character. Why did log10.txt drop out?
Linny-senpai: Because log?.txt means "log, then exactly one character, then .txt." log10.txt has two characters (1 and 0), so it doesn't match.
Lina: And if I want two characters?
Linny-senpai: Just write ??. 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
Lina: So [0-9] means "0 through 9." Handy! Can I do letters too?
Linny-senpai: Sure. [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}.txt
file1.txt file2.txt file3.txt
Lina: Hold on, isn't this the same as globbing?
Linny-senpai: It looks similar but it's completely different. {} just builds strings and does not check whether the files exist. So it produces names even for files that aren't there.
Lina: While globbing only expands to files that actually exist.
Linny-senpai: Right. Think of {} 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 5

When * 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
Lina: I get the basic symbols. But can I write something like "only .jpg and .png"?
Linny-senpai: With standard globbing that's awkward, but if you enable extglob (extended globbing) it gets much easier. Use @(...) 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
Lina: It's looking for a file literally named *.xml? That's a weird error.
Linny-senpai: By default bash leaves the pattern unexpanded when nothing matches, passing it to 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.

Lina: I've got the theory! I want to try it hands-on.
Linny-senpai: Great, I prepared three tasks. Create the practice files first, then give them a go.
# 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.

Next Reading