pathlib glob() Matches Hidden Dotfiles, glob Module Does Not (tested)
Shell globbing and Python's glob module both skip dot-prefixed files by convention. We measured pathlib.Path.glob() and Path.rglob() on the same directory and found they don't follow that convention at all -- dotfiles and dot-directories match like any other name.
1. Same pattern, different results
from pathlib import Path
import glob
base = Path("/tmp/globtest")
# layout: visible.txt, .hidden.txt, sub/normal.txt, .hidden/secret.txt
print("pathlib glob(*.txt):", sorted(p.name for p in base.glob("*.txt")))
print("glob.glob(*.txt): ", sorted(glob.glob("*.txt", root_dir=base)))
pathlib glob(*.txt): ['.hidden.txt', 'visible.txt']
glob.glob(*.txt): ['visible.txt']
Measured: the identical pattern "*.txt" matches the hidden file under pathlib but not under the glob module. No warning, no flag to toggle -- the two stdlib APIs just disagree on whether a leading dot is special.
2. The gap gets wider with recursion
print("pathlib rglob(*.txt):", sorted(str(p.relative_to(base)) for p in base.rglob("*.txt")))
print("glob.glob(**/*.txt):", sorted(glob.glob("**/*.txt", root_dir=base, recursive=True)))
pathlib rglob(*.txt): ['.hidden/secret.txt', 'sub/normal.txt', 'visible.txt']
glob.glob(**/*.txt): ['sub/normal.txt', 'visible.txt']
Measured: Path.rglob() walks straight into the .hidden directory and returns the file inside it. glob.glob(..., recursive=True) never descends into .hidden at all -- it is excluded at the directory level, not just the filename level, matching how a shell's ** behaves.
3. Fix: filter dot-names explicitly if you need shell-like behaviour
def is_hidden(p):
return any(part.startswith(".") for part in p.relative_to(base).parts)
visible_only = [p for p in base.rglob("*.txt") if not is_hidden(p)]
print(sorted(str(p.relative_to(base)) for p in visible_only))
['sub/normal.txt', 'visible.txt']
Measured: pathlib.Path.glob()/rglob() take no include_hidden parameter at all in 3.13.5 -- there's no built-in switch to opt into shell-like behaviour. The glob module's own glob.glob() does have an include_hidden keyword (added in 3.11), but it defaults to False, which is just the already-measured default -- it only helps if you want to go the other way and make glob.glob() match dotfiles too. Filtering on str.startswith(".") per path segment is the straightforward way to restore the skip-dotfiles convention when using pathlib.
Frequently asked questions
Does Path.glob() skip hidden files by default?
No -- we measured it matching dot-prefixed files and directories with a plain '*' or '*.ext' pattern, with no dotfile-skipping behaviour built in.
Does the glob module skip hidden files?
Yes -- we confirmed glob.glob() excludes dot-prefixed names from '*' matches and refuses to descend into dot-prefixed directories even with recursive=True, matching shell glob conventions.
Will Path.rglob() recurse into a hidden directory?
Yes -- we measured it walking into a directory named '.hidden' and returning matching files inside, where glob.glob(..., recursive=True) excluded that directory entirely.
Is there a pathlib option to skip dotfiles like glob does?
No -- we confirmed Path.glob() and Path.rglob() take no include_hidden parameter in Python 3.13.5. The glob module has that keyword, but pathlib doesn't; filtering on a leading dot yourself is the only way to match glob's default.
Why does this matter for scripts migrating from glob to pathlib?
A script rewritten from glob.glob() to Path.glob() can silently start picking up dotfiles (like .git, .env, or editor swap files) that the original never touched, with no error to flag the change.
Written with AI assistance. Measured on 2026-10-04 with Python 3.13.5's built-in pathlib and glob modules: created a directory with a mix of hidden (dot-prefixed) and visible files and subdirectories, then compared Path.glob()/Path.rglob() against glob.glob() with recursive=True on the same patterns. PlainHeaders is our extension; Python's pathlib and glob modules are built in.