Skip to Content
Course content

246: The glob and fnmatch Modules

Click on the "Edit" button in the top corner of the screen to edit your slide content.

I've seen this pattern a dozen times in code reviews. A developer needs to process a few specific files in a folder, so they use os.listdir() and a simple if statement to filter them. It seems fine until the project grows and the file naming gets messy.

The "Contains" Trap

import os

# Goal: Process all JPEG images in the current directory
files = os.listdir('.')
images = [f for f in files if '.jpg' in f]

for img in images:
    print(f"Processing image: {img}")

At first glance, this looks okay. But here is the bug: if you have a file named notes_about_jpg_compression.txt or a folder called jpg_backups, this code will try to "process" them as images. Using in is too blunt; it doesn't care where the string appears. You only want files that end with that extension, and you probably want to ignore case (so .JPG doesn't get skipped).

You could fix this with f.lower().endswith('.jpg'), but once you need more complex patterns—like "any file that starts with 'data', ends in '.csv', and has a date in the middle"—your if statements will become an unreadable mess of string slicing.

Finding Files with glob

This is where glob comes in. Instead of listing everything and filtering it yourself, glob lets you ask the filesystem for exactly what you want using Unix-style wildcards. The * character matches zero or more characters.

import glob

# This returns a list of paths that actually match the pattern
images = glob.glob('*.jpg') 

# If you want to be case-insensitive on systems that care, 
# or handle multiple extensions, you can use brackets:
images = glob.glob('*. [jJ][pP][gG]') # a bit clunky
# Or better yet, just run multiple globs or use fnmatch for the heavy lifting.

The biggest difference here is that glob returns the path to the file, not just the filename. If you search in 'logs/*.txt', you get 'logs/error.txt', which saves you from having to use os.path.join() later.

Precise Filtering with fnmatch

Sometimes you already have a list of filenames (maybe from a database or a different API) and you just need to check if they match a pattern without hitting the disk again. That's where fnmatch is your best friend. It provides the same wildcard logic as glob but works on strings.

import fnmatch

filenames = ['data_2023_01.csv', 'data_2023_02.csv', 'readme.txt', 'config.json']

# We only want the 2023 CSVs
csv_2023 = [f for f in filenames if fnmatch.fnmatch(f, 'data_2023_*.csv')]

print(csv_2023) # ['data_2023_01.csv', 'data_2023_02.csv']

I prefer fnmatch.fnmatch() because it's case-insensitive by default on Windows and case-sensitive on Linux (following the OS behavior). If you want to force a specific behavior regardless of the platform, use fnmatch.fnmatchcase().

Hunting through Subdirectories

One thing that trips people up is that glob.glob('*.txt') only looks in the current folder. If your project has a nested structure, you need the "recursive" flag. To make this work, you use the ** pattern, which means "this directory and all directories beneath it."

import glob

# Find every .py file in the project, no matter how deep it is
all_python_files = glob.glob('**/*.py', recursive=True)

Just a heads up: be careful with recursive=True in massive directory trees (like your entire home directory). It can take a long time to return because it's physically walking through every single folder on your drive.




📋 Practical Task

Log File Archive Filter

You are building a cleanup script for a server. The server generates logs with names like server_2023_01.log, server_2023_02.log, client_2023_01.log, and system_2022_12.log.

Write a Python script that does the following:

  • Create a list of dummy filenames in a list variable (since you aren't interacting with a real filesystem for this exercise).
  • Use the fnmatch module to filter that list and find only the logs that belong to the year 2023 and start with either server or client.
  • Print the final filtered list.

Example Input: ['server_2023_01.log', 'client_2023_05.log', 'system_2023_01.log', 'server_2022_11.log', 'notes.txt']
Expected Output: ['server_2023_01.log', 'client_2023_05.log']

Rating
0 0

There are no comments for now.

to be the first to leave a comment.