Matching Paths and Filenames with glob and fnmatch
Learn when to use glob to find pathnames and fnmatch to filter names, including recursive searches, hidden entries, case behavior, and literal wildcard characters.
Intermediate
Python has several ways to find or select names using shell-style wildcard patterns. The standard-library glob module finds pathnames in a directory tree; fnmatch tests strings that represent names. These tools are related, but they answer different questions from pathlib.Path.glob(), which returns Path objects.
Read wildcard patterns as patterns, not regular expressions
The common wildcard symbols are * for zero or more characters, ? for exactly one character, [seq] for one character from a set, and [!seq] for one character not in a set. This is shell-style wildcard syntax, not regular-expression syntax: for example, *.csv is not a regular expression.
Output
Ready to run.
Find paths with glob
glob.glob() expands a pathname pattern and returns matching pathnames as strings. Pass root_dir when you want a search rooted at a directory without changing the process working directory. Relative patterns return names relative to that root. File-system order is not guaranteed, so sort matches whenever output, reports, or tests need predictable order.
Output
Ready to run.
Search a tree recursively
The ** pattern can match zero or more directory levels, but glob.glob() uses recursive matching only when recursive=True. Recursive patterns can scan many directories, take substantial time, and in some patterns produce duplicate pathnames. Narrow root_dir and patterns before searching a large tree.
Output
Ready to run.
Process matches lazily with iglob
glob.iglob() yields matches through an iterator instead of building a list of every pathname first. That can reduce memory use for large searches if you process each result as it arrives. It does not remove the cost of scanning the tree, and it has the same pattern and filesystem-error behavior as glob().
Output
Ready to run.
Decide whether hidden names should match
By default, glob patterns such as *.txt do not match path components beginning with a dot. On Python 3.11 and later, include_hidden=True lets wildcard patterns match hidden path components too. Choose this option deliberately: hidden files may contain configuration, local state, or private data that should not be included in a bulk operation.
Output
Ready to run.
Filter filename strings with fnmatch
fnmatch.filter(names, pattern) selects matching strings from an iterable of filename-like values. fnmatch.fnmatch() applies operating-system case normalization, so case behavior can vary by platform. Use fnmatch.fnmatchcase() when the comparison must always be case-sensitive. Unlike path globbing, fnmatch treats the path separator as an ordinary character: its job is to test a string, not to walk directories.
Output
Ready to run.
Match literal names containing wildcard characters
When a path or name comes from data and contains characters such as *, ?, or [, those characters would normally be interpreted as pattern syntax. glob.escape() protects such characters in a pathname pattern so that the search can find a literal name. It is not a general-purpose shell-escaping function.
Output
Ready to run.
Choose the right tool and understand its limits
Use pathlib.Path.glob() when you want Path objects and are already working with path objects. Use glob.glob() for a list of pathname strings, glob.iglob() for streaming matches, and fnmatch when filtering names already held in memory. glob suppresses OSError exceptions raised while scanning directories; if your task must report every inaccessible directory or prove a complete scan, do not assume glob alone provides that guarantee. For recursive searching, keep both the root and the pattern as narrow as the task allows.
Find names directly under a root
Complete the call so glob returns CSV pathnames rooted at root without changing the working directory.
import glob
from pathlib import Path
from tempfile import TemporaryDirectory
with TemporaryDirectory() as temporary:
root = Path(temporary)
(root / "one.csv").touch()
(root / "two.txt").touch()
matches = sorted(glob.glob("*.csv", ))
assert matches == ["one.csv"]
print(matches)
Enable recursive ** matching
Complete the call so the double-star pattern also matches CSV files in child directories.
import glob
from pathlib import Path
from tempfile import TemporaryDirectory
with TemporaryDirectory() as temporary:
root = Path(temporary)
(root / "nested").mkdir()
(root / "nested" / "item.csv").touch()
matches = glob.glob("**/*.csv", root_dir=str(root), )
assert len(matches) == 1 and Path(matches[0]).name == "item.csv"
print(matches[0])
Include hidden path entries
Complete the argument so *.txt can match both notes.txt and .draft.txt.
import glob
from pathlib import Path
from tempfile import TemporaryDirectory
with TemporaryDirectory() as temporary:
root = Path(temporary)
(root / "notes.txt").touch()
(root / ".draft.txt").touch()
matches = set(glob.glob("*.txt", root_dir=str(root), ))
assert matches == {"notes.txt", ".draft.txt"}
print(sorted(matches))
Choose a stable case-sensitive test
Complete the expression so REPORT.CSV does not match the lowercase pattern *.csv regardless of operating system.
import fnmatch
assert not fnmatch.("REPORT.CSV", "*.csv")
print("case-sensitive mismatch confirmed")
Protect a literal pattern character
Complete the expression so the brackets in release[final].csv are treated as literal characters.
import glob
name = "release[final].csv"
pattern =
assert pattern == glob.escape(name)
print(pattern)
Thanks for your feedback.
Mark this learning resource complete to track your learning progress.