Python fnmatch: Match Filenames with Wildcards

Quick answer: The fnmatch module matches strings against shell-style wildcard patterns. Use * for any sequence, ? for one character, and bracket expressions for character classes. fnmatch is useful for a list of names, but it is not a complete recursive path policy, so choose pathlib, glob, or os.walk when directory traversal matters.

Python Pool infographic comparing fnmatch patterns star question mark character classes filter and path handling
fnmatch applies shell-style filename patterns such as *.py and report-??.csv; choose the matching function and path scope deliberately.

The Python fnmatch library matches strings against Unix shell-style wildcard patterns. It is useful when you already have file names, user-supplied names, or directory entries and want to check whether each one fits a simple pattern. The official Python fnmatch documentation describes these rules as shell-style wildcards, not full regular expressions.

The key point is that fnmatch matches names; it does not walk directories by itself. Use os.listdir(), pathlib, or glob to collect paths, then use fnmatch when you need direct pattern checks on those names. For directory basics, see the Python current directory guide.

The common wildcard characters are * for any sequence, ? for one character, [abc] for one allowed character, and [!abc] for one excluded character. These patterns are intentionally small and readable. If you need advanced text matching, translate the pattern or use re directly.

Use these patterns for file names and simple labels, not for parsing structured text. That keeps the rule obvious to the next reader and avoids turning a small wildcard check into a hidden validation system.

Match A File Name With fnmatch()

fnmatch.fnmatch() returns True or False for one name and one pattern. On systems with case-insensitive file names, Python may normalize case before comparing. That makes it convenient for everyday file-name checks.

from fnmatch import fnmatch

names = ["report.txt", "notes.md", "data.txt", "script.py"]
text_files = [name for name in names if fnmatch(name, "*.txt")]

print(text_files)

This is the simplest form for filtering a list that is already in memory. The pattern *.txt means any name ending with that extension. The function does not open the files and does not check whether they exist.

Use fnmatchcase() For Exact Case

fnmatch.fnmatchcase() skips operating-system case normalization and compares the pattern exactly as written. Use it when uppercase and lowercase should be treated differently across every platform.

from fnmatch import fnmatchcase

names = ["DATA.csv", "data.csv", "Data.csv"]
matches = [name for name in names if fnmatchcase(name, "data*.csv")]

print(matches)

This example only keeps the lowercase name because the pattern starts with lowercase data. If your application needs predictable behavior on Windows, macOS, and Linux, choose fnmatchcase() when case must be strict.

Python Pool infographic showing a filename, wildcard pattern, fnmatch, and Boolean result
fnmatch compares a filename with shell-style wildcard patterns.

Filter Names With fnmatch.filter()

fnmatch.filter() applies one pattern to a list of names and returns the matching names. It is a concise alternative to writing the same list comprehension yourself.

from fnmatch import filter

names = ["main.py", "README.md", "test_api.py", "config.toml"]
python_files = filter(names, "*.py")

print(python_files)

This function is useful for short, direct filters. If you need to apply extra checks, combine patterns, or transform the names while looping, a normal for loop or list comprehension is often clearer.

Scan A Directory With pathlib

Because fnmatch does not search the filesystem by itself, a common pattern is to list a directory first and then match each path name. The pathlib documentation covers the modern path API used here.

from fnmatch import fnmatch
from pathlib import Path

root = Path(".")
matches = [path.name for path in root.iterdir() if fnmatch(path.name, "test_*.py")]

print(matches)

This keeps filesystem access separate from pattern matching. Use path.name when the pattern should match only the final file name. Use the full path string only when the directory segments are intentionally part of the pattern.

Python Pool infographic comparing star, question mark, character class, and filename matches
Shell-style wildcards express broad, single-character, and character-class matches.

Translate A Pattern To Regex

fnmatch.translate() converts a shell-style pattern into a regular expression string. This is useful when you want to compile the translated pattern once and reuse it many times.

from fnmatch import translate
import re

pattern = "data_[0-9][0-9].csv"
regex = re.compile(translate(pattern))

print(bool(regex.match("data_24.csv")))
print(bool(regex.match("data_final.csv")))

The translated expression is generated for you, so you do not need to hand-write a regular expression for a simple wildcard pattern. If your matching rules are already complex, start with the re module instead of forcing them through fnmatch.

Match Multiple Patterns

fnmatch.filter() accepts one pattern at a time. To match multiple patterns, test each name with any(). This is clearer than trying to pass a list of patterns into filter().

from fnmatch import fnmatch

names = ["main.py", "notes.txt", "archive.zip", "README.md"]
patterns = ["*.py", "*.txt"]

matches = [name for name in names if any(fnmatch(name, pattern) for pattern in patterns)]

print(matches)

Use the same idea for exclusions by negating a match. For example, keep names where not fnmatch(name, "*.tmp") is true. This keeps include and exclude rules explicit.

fnmatch vs glob

fnmatch checks a string against a pattern. glob searches the filesystem and returns matching paths. Use glob when the pattern itself should find files, and use fnmatch when you already have names and want a lightweight matcher. The glob documentation explains recursive path expansion and filesystem search behavior.

For most file-management scripts, a practical split is simple: collect names with pathlib, os.listdir(), or glob, then filter with fnmatch if the selection logic is easiest to express with shell-style wildcards. Keep in mind that fnmatch does not validate user input for security by itself. It only answers whether a string matches a pattern.

The fnmatch module is best for readable wildcard checks such as *.py, data_??.csv, and test_*. Reach for fnmatchcase() when case must be exact, filter() for one-pattern list filtering, and translate() when a wildcard pattern needs to become a compiled regular expression.

Python Pool infographic mapping a directory listing through fnmatch filter to selected files
Filter directory names with a pattern when only matching filenames are needed.

Match Common Filename Patterns

fnmatch() answers whether one name matches a pattern. Patterns describe the whole string, so include the suffix or prefix you intend to accept. Keep patterns readable and treat them as data when they come from configuration.

from fnmatch import fnmatch

names = ["app.py", "README.md", "test_api.py", "data.csv"]
for name in names:
    if fnmatch(name, "*.py"):
        print(name)

Use Wildcards And Character Classes

An asterisk matches any sequence, a question mark matches one character, and bracket expressions match a selected character set or range. Test representative names, including empty or unusually long names, before using a pattern to include or delete files.

from fnmatch import fnmatch

patterns = ["report-??.csv", "image[0-9].png", "[!_]*"]
values = ["report-01.csv", "image7.png", "_private.txt"]
for pattern in patterns:
    print(pattern, [value for value in values if fnmatch(value, pattern)])
Python Pool infographic testing case sensitivity, paths, hidden files, and validation
Check case behavior, path components, hidden files, and the intended pattern scope.

Filter Lists And Control Case

fnmatch.filter() is convenient for a list of names. fnmatch() applies operating-system case normalization, while fnmatchcase() always compares case exactly. Decide whether a pattern such as *.PY should match file.py, and test on the systems where the result matters.

import fnmatch

names = ["README", "readme", "Readme"]
print(fnmatch.filter(names, "readme"))
print([name for name in names if fnmatch.fnmatchcase(name, "readme")])

Keep Path Boundaries Explicit

fnmatch treats its input as a string and does not turn a wildcard into a safe recursive file operation. Match Path.name when only the filename should matter, or use a path-aware traversal and apply a clear root, extension, and symlink policy before mutating files.

from pathlib import Path
from fnmatch import fnmatch

root = Path("reports")
for path in root.rglob("*"):
    if path.is_file() and fnmatch(path.name, "*.csv"):
        print(path)

Python’s fnmatch documentation defines shell-style patterns, case handling, filter(), and translate(). For recursive discovery, compare the path-aware behavior of glob before using a string match as a filesystem operation.

For related filename workflows, compare os.listdir(), file-existence checks, and path creation with pathlib when a wildcard match becomes a filesystem operation.

Frequently Asked Questions

What is Python fnmatch used for?

It matches strings such as filenames against shell-style wildcard patterns including *, ?, and character classes in square brackets.

What is the difference between fnmatch() and fnmatchcase()?

fnmatch follows operating-system case-normalization rules, while fnmatchcase performs a case-sensitive match without normalizing case.

How do I filter filenames with fnmatch?

Use fnmatch.filter(names, pattern) for a list of names, or iterate and call fnmatch() when each result needs additional handling.

Can fnmatch match full paths?

It matches strings, but path separators are not special to fnmatch; use pathlib, glob, or a path-aware policy when directory boundaries matter.

Subscribe
Notify of
guest
0 Comments
Oldest
Newest Most Voted