Python fnmatch — Core Concepts

What fnmatch does

The fnmatch module provides Unix shell-style wildcard matching for strings — primarily filenames. It’s simpler than regular expressions and covers the common case of “find files matching a pattern.”

Pattern syntax

PatternMeaningExample match
*Any number of characters (including zero)*.pyapp.py, test.py
?Exactly one characterdata_?.csvdata_A.csv, not data_AB.csv
[seq]Any one character in seqfile[123].txtfile1.txt, file2.txt
[!seq]Any one character NOT in seqlog[!0-9].txtlogA.txt, not log5.txt

The * wildcard does not match path separators — *.py won’t match subdir/app.py. For recursive path matching, use pathlib.Path.glob() or the glob module.

The four functions

fnmatch.fnmatch(filename, pattern)

The primary function. Returns True if the filename matches the pattern. On Windows, matching is case-insensitive; on Unix, it’s case-sensitive.

import fnmatch

fnmatch.fnmatch("report_2024.csv", "report_*.csv")   # True
fnmatch.fnmatch("image.PNG", "*.png")                  # True on Windows, False on Linux

fnmatch.fnmatchcase(filename, pattern)

Always case-sensitive, regardless of platform. Use this when you need consistent behavior:

fnmatch.fnmatchcase("image.PNG", "*.png")   # False everywhere
fnmatch.fnmatchcase("image.png", "*.png")   # True everywhere

fnmatch.filter(names, pattern)

Filters a list of names, returning only those that match. More efficient than calling fnmatch() in a loop because it pre-compiles the pattern:

files = ["app.py", "test.py", "README.md", "config.yaml", "utils.py"]
python_files = fnmatch.filter(files, "*.py")
# ['app.py', 'test.py', 'utils.py']

fnmatch.translate(pattern)

Converts a fnmatch pattern to a regular expression string. Useful when you need the pattern matching elsewhere:

fnmatch.translate("*.py")
# '(?s:.*\\.py)\\Z'

Practical use cases

Filtering directory listings

import os, fnmatch

for name in os.listdir("data/"):
    if fnmatch.fnmatch(name, "*.csv"):
        print(f"Processing {name}")

Multi-pattern matching

fnmatch doesn’t support OR patterns natively, but you can combine with any():

patterns = ["*.py", "*.js", "*.ts"]
source_files = [f for f in all_files if any(fnmatch.fnmatch(f, p) for p in patterns)]

Configuration-driven file selection

IGNORE_PATTERNS = ["*.pyc", "__pycache__", "*.egg-info", ".git"]

def should_include(filename):
    return not any(fnmatch.fnmatch(filename, pat) for pat in IGNORE_PATTERNS)

fnmatch vs. glob vs. regex

Featurefnmatchglobregex
Matches filenames
Traverses directories
Recursive matching✅ (**)
Pattern complexityLowLowHigh
Learning curveMinimalMinimalSteep

Use fnmatch when you already have a list of names and need to filter them. Use glob when you want to find files on disk. Use regex when wildcard patterns aren’t expressive enough.

Common misconception

fnmatch patterns look like glob patterns but they only match against a single filename component — they don’t understand directory paths. fnmatch.fnmatch("src/app.py", "*.py") returns True because * matches everything including /. This can surprise you if you expect * to stop at directory separators like glob does.

One thing to remember

fnmatch is the sweet spot between “check if a string ends with .py” and full regular expressions — readable patterns for filename matching, with filter() for efficient batch operations.

pythonstandard-libraryfile-handling

See Also

  • Python Atexit How Python's atexit module lets your program clean up after itself right before it shuts down.
  • Python Bisect Sorted Lists How Python's bisect module finds things in sorted lists the way you'd find a word in a dictionary — by jumping to the middle.
  • Python Contextlib How Python's contextlib module makes the 'with' statement work for anything, not just files.
  • Python Copy Module Why copying data in Python isn't as simple as it sounds, and how the copy module prevents sneaky bugs.
  • Python Dataclass Field Metadata How Python dataclass fields can carry hidden notes — like sticky notes on a filing cabinet that tools read automatically.