Home› Tutorials› Standard Library
Standard LibraryPython's glob Module: Pattern Matching Across the Filesystem
glob.glob("*.csv")expands shell-style wildcards against the filesystem and returns a list of matching paths, in arbitrary order.**only means "any number of directories deep" whenrecursive=Trueis passed — without it,**behaves just like a single*.- Patterns starting with a literal dot are required to match dotfiles; a bare
*silently skips them, mirroring shell glob behaviour on POSIX systems. iglob()returns a generator instead of a list, which matters when scanning a directory tree too large to comfortably hold every match in memory at once.
Basic wildcards: *, ?, and [seq]
glob expands the same wildcard syntax a Unix shell uses, matched against actual files and directories on disk:
import glob
glob.glob("data/*.csv") # every .csv file directly inside data/
glob.glob("report_202?.txt") # report_2020.txt, report_2021.txt, ...
glob.glob("img[0-3].png") # img0.png through img3.png
* matches any run of characters (but not a path separator), ? matches exactly one character, and [seq] matches any single character in the given set or range. Unlike regular expressions, glob patterns are deliberately simpler — there's no alternation, no repetition counts, no capturing groups. That's a feature for this use case: filesystem patterns are almost always this simple, and reaching for full regex to match "every .csv file" would be needless overhead.
Recursive matching with ** and recursive=True
By default, * and even ** stop at a single directory level. Genuine recursive descent needs an explicit flag:
glob.glob("project/**/*.py", recursive=True)
With recursive=True, a ** segment matches zero or more directory levels, so the pattern above finds every .py file anywhere under project/, including directly inside it. Leaving off recursive=True silently degrades ** to behave exactly like a plain * — it will not raise an error or a warning, it will just quietly under-match, which is the most common bug in code that adds recursive lookup after the fact and forgets the flag.
iglob(): lazy matching for large trees
glob.glob() walks the entire matching set before returning anything, building a full list in memory. iglob() does the identical matching but yields results one at a time as a generator:
for path in glob.iglob("logs/**/*.log", recursive=True):
process_log(path)
if enough_processed():
break
This matters on a directory tree with hundreds of thousands of files, where materialising the full list before processing starts wastes memory and delays the first result. It's the same trade-off as generators versus building a list up front — iglob() is almost always the better default when the results are going to be consumed one at a time anyway rather than needing to be sorted, counted, or indexed as a whole.
Why hidden files don't show up
A bare * pattern does not match filenames that start with a dot:
glob.glob("*") # skips .env, .gitignore, etc.
glob.glob(".*") # matches only dotfiles
This mirrors the historical behaviour of Unix shell globbing, where dotfiles are treated as hidden by convention and excluded from wildcard expansion unless the pattern itself starts with a dot. It's easy to forget when a script that's supposed to process "every file in this config directory" quietly skips a dotfile config and nobody notices until something depending on it fails.
glob vs os.listdir vs pathlib.glob
os.listdir() returns every entry in one directory with no pattern filtering at all — useful when the filter logic is more complex than glob syntax can express, since you get plain names back and can apply any Python condition. os.walk(), covered in the os module guide, is the tool for recursive traversal when you need the directory structure itself, not just a flat list of matches. pathlib.Path.glob() and Path.rglob() offer the same pattern matching as this module but return Path objects instead of plain strings, which is convenient when the next step is calling Path methods like .stem or .parent on each result rather than re-wrapping a string. For a quick one-off wildcard match returning plain path strings, glob remains the shortest route to the answer.
One more practical note: none of glob's matching guarantees any particular order. The results come back in whatever order the underlying filesystem happens to return directory entries, which varies by operating system and even by filesystem type, and is not alphabetical on most systems despite how it often appears to be for small directories during casual testing. Code that needs a predictable, repeatable order — for a build step whose output should be identical across machines, or for tests that compare against a fixed expected list — needs to sort the results explicitly with sorted(glob.glob(pattern)) rather than relying on whatever order happened to come back. This is an easy detail to miss during development on one machine and then get bitten by later when the same code runs somewhere else with a different filesystem underneath it.