Python fnmatch: Filter File Names

Published on: August 10, 2026
Reading time: 5 minutes
Folder and magnifying glass representing file-name filters with Python fnmatch

The fnmatch module in Python’s standard library compares file names against Unix shell-style wildcard patterns. It is useful when an application already has a collection of names and needs to select entries such as *.py, report-202?.csv, or image-[0-9].png. Unlike glob, it does not walk directories. Unlike re, its patterns are not regular expressions.

This guide explains fnmatch(), fnmatchcase(), filter(), filterfalse(), and translate(), with portability, performance, and security practices.

Understanding wildcard syntax

The syntax has four main constructs. An asterisk matches any number of characters, a question mark matches exactly one character, [abc] matches one character from a sequence, and [!abc] excludes those characters.

import fnmatch

print(fnmatch.fnmatch("report-2026.csv", "report-*.csv"))
print(fnmatch.fnmatch("photo-7.jpg", "photo-?.jpg"))
print(fnmatch.fnmatch("log-a.txt", "log-[abc].txt"))

To match a metacharacter literally, place it inside brackets. The pattern [?], for example, matches an actual question mark.

Filtering an existing collection

fnmatch.filter() accepts an iterable of names and returns only matching items. It communicates intent clearly and is implemented more efficiently than repeatedly writing the same comprehension.

from fnmatch import filter

names = ["app.py", "test.py", "README.md", "data.csv"]
python_files = filter(names, "*.py")
print(python_files)

This approach works with os.listdir(), API results, database records, archive entries, object-storage keys, and file names received from another service.

Excluding matches with filterfalse

Python 3.14 added filterfalse(), which returns names that do not match the pattern. It removes repetitive negated comprehensions and makes exclusion policies easier to read.

from fnmatch import filterfalse

files = ["a.tmp", "b.txt", "c.log", "d.tmp"]
permanent = filterfalse(files, "*.tmp")
print(permanent)

On older Python versions, use a comprehension with not fnmatch.fnmatch(name, pattern).

Case sensitivity across operating systems

fnmatch() applies os.path.normcase() to both the name and pattern. The result can therefore vary by operating system. On systems that normalize case, FILE.TXT may match *.txt; on case-sensitive systems it usually will not.

Use fnmatchcase() when the policy must behave identically on Linux, Windows, macOS, containers, and CI machines. It performs a case-sensitive comparison without operating-system normalization.

from fnmatch import fnmatchcase

allowed = fnmatchcase("Report.CSV", "*.csv")
print(allowed)  # False

Directory separators are ordinary characters

The slash character is not special to fnmatch. An asterisk may match across separators that appear in the string. This differs from pathname expansion, where glob processes path segments.

import fnmatch

print(fnmatch.fnmatch("data/2026/sales.csv", "*.csv"))

Use pathlib.Path.glob() or glob when you need filesystem traversal, recursion, or segment-aware expansion. Use fnmatch when you already have names and only need text matching.

Hidden files are not treated specially

Leading periods have no special behavior. A pattern such as * may match .env, .gitignore, and other hidden names. Applications must define their hidden-file policy explicitly.

def visible(name):
    return not name.startswith(".")

selected = [n for n in names if visible(n) and fnmatch.fnmatch(n, "*")]

fnmatch is not a regular expression engine

The wildcard pattern *.txt is valid in fnmatch, while in a regular expression the asterisk modifies the preceding token. Regex groups, lookarounds, captures, and numeric quantifiers are not available in shell patterns.

Wildcards are usually better for understandable file-name rules. Use re when validation requires captures, anchors, repeated groups, or complex structural conditions.

Translating a pattern to regex

translate() converts shell-style syntax into a regular expression. It is useful when you want to compile the result, integrate it into another regex-based step, or inspect the generated rule.

import fnmatch
import re

regex = re.compile(fnmatch.translate("report-*.csv"))
print(bool(regex.match("report-july.csv")))

Treat the translated expression as an implementation detail. Do not depend on its exact string representation across Python versions.

Pattern caching and performance

The primary functions cache typed compiled regular expressions with a maximum size of 32,768 patterns. Reusing a small stable pattern set is efficient. Supplying a constant stream of unique user-generated patterns reduces cache benefits and creates extra compilation work.

For public services, limit pattern length and count. Avoid processing huge collections against thousands of arbitrary patterns in one request. Apply cheap filters first, paginate results, and measure realistic workloads.

Strings and bytes

The API supports Unicode strings or ISO-8859-1 encoded bytes, but the name and pattern must use the same type. Mixing a bytes name with a string pattern raises an error. Modern applications should normally normalize input to Unicode.

import fnmatch

print(fnmatch.fnmatch(b"data.csv", b"*.csv"))
# fnmatch.fnmatch(b"data.csv", "*.csv")  # TypeError

Security boundaries

fnmatch only compares text. It does not verify that a file exists, prevent path traversal, enforce permissions, or guarantee that a path remains inside an approved directory. A successful match must never be the only authorization check before reading, uploading, moving, or deleting a file.

Resolve paths with pathlib, verify the trusted base directory, reject unexpected path components, and rely on real access controls. For destructive batch operations, show a preview and log the selected names.

Example: selecting application logs

from fnmatch import fnmatchcase
from pathlib import Path

BASE = Path("logs").resolve()
PATTERNS = ("app-*.log", "worker-*.log")

def select_logs():
    result = []
    for path in BASE.iterdir():
        if not path.is_file():
            continue
        if any(fnmatchcase(path.name, p) for p in PATTERNS):
            result.append(path)
    return result

The code compares only path.name, so directory text cannot change the matching semantics. The application controls the patterns and separately verifies that each result is a file.

Common mistakes

  • Expecting the same case behavior on every operating system.
  • Confusing wildcard syntax with regular expressions.
  • Assuming an asterisk stops at directory separators.
  • Forgetting that hidden names match ordinary wildcards.
  • Using a match as an access-control decision.
  • Mixing strings and bytes.
  • Using fnmatch for directory traversal instead of glob.
  • Choose fnmatchcase() for portable policies.
  • Match only the basename when directories are irrelevant.
  • Define hidden-file handling explicitly.
  • Limit user-supplied patterns.
  • Validate paths and permissions separately.
  • Test Unicode, casing, empty names, and boundaries.
  • Use filter() and filterfalse() for whole collections.

Continue with our guides to Python fileinput, Python linecache, Python shlex, Python filecmp, and Python bisect.

Authoritative references are the fnmatch documentation and the glob documentation.

Conclusion

fnmatch is a focused solution for comparing existing names with readable wildcard rules. Its small API, compiled-pattern cache, and Python 3.14 exclusion helper make it effective for filters and policies. Correct use requires separating wildcard matching from regex, filesystem traversal, authorization, and path validation.

Share:

Facebook
WhatsApp
Twitter
LinkedIn

Article content

    Related articles

    Monitor with binary data representing compact numeric arrays in Python
    Libraries and Modules
    Foto de perfil de Leandro Hirt da Academify

    Python array: Compact Numeric Data

    Learn Python array for compact numeric storage, binary files, byte order, memory views, and safe buffer interoperability.

    Ler mais

    Tempo de leitura: 6 minutos
    10/08/2026
    Color wheel representing RGB, HSV, and HLS conversions with Python colorsys
    Libraries and Modules
    Foto de perfil de Leandro Hirt da Academify

    Python colorsys: RGB, HSV, and HLS

    Learn Python colorsys to convert colors between RGB, HSV, HLS, and YIQ, generate palettes, and avoid scale and precision mistakes.

    Ler mais

    Tempo de leitura: 5 minutos
    09/08/2026
    Configuration icon representing plist files with Python plistlib
    Libraries and Modules
    Foto de perfil de Leandro Hirt da Academify

    Python plistlib: Apple Plist Files

    Learn Python plistlib to read and write XML and binary plist files, validate data, handle dates, bytes, and UIDs safely.

    Ler mais

    Tempo de leitura: 6 minutos
    08/08/2026
    Digital padlock representing host credentials with Python netrc
    Libraries and Modules
    Foto de perfil de Leandro Hirt da Academify

    Python netrc: Credentials by Host

    Learn Python netrc to read credentials by host, validate permissions, handle parse errors, and integrate network clients safely.

    Ler mais

    Tempo de leitura: 6 minutos
    08/08/2026
    Digital message representing quoted-printable encoding with Python quopri
    Libraries and Modules
    Foto de perfil de Leandro Hirt da Academify

    Python quopri: Quoted-Printable

    Learn Python quopri to encode and decode quoted-printable data in email, files, and MIME integrations safely.

    Ler mais

    Tempo de leitura: 6 minutos
    08/08/2026
    Digital file icon representing MIME types with Python mimetypes
    Libraries and Modules
    Foto de perfil de Leandro Hirt da Academify

    Python mimetypes: MIME Types

    Learn Python mimetypes to identify MIME types, extensions, and encodings safely in uploads, downloads, email, and web APIs.

    Ler mais

    Tempo de leitura: 6 minutos
    08/08/2026