Finding Files in Python With pathlib glob and rglob
glob() searches one folder, rglob() searches every folder below it. Neither sorts the results, skips folders like .venv, or matches .CSV on Linux. Add those yourself, and always do a dry run before deleting anything.
To find files in Python, use Path.glob() for one folder and Path.rglob() for a folder and everything inside it. They replace most uses of os.listdir, os.walk and the glob module, and they give you Path objects you can use straight away. There are three things they don't do for you: sort the results, skip folders you don't want, and stop you from deleting the wrong files.
Skip ahead: If you only need the syntax: Path("reports").glob("*.csv") for one folder, Path("reports").rglob("*.csv") for all subfolders. Read "Sort the results" before you rely on the order.
A folder to search
from pathlib import Path
files = [
"reports/2026-01.csv",
"reports/2026-02.csv",
"reports/summary.txt",
"reports/archive/2025-12.csv",
"reports/archive/old/2025-01.CSV",
"reports/.venv/lib/site.csv",
]
for name in files:
path = Path(name)
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text("date,total\n", encoding="utf-8")
print("ready")
ready
glob: one folder
from pathlib import Path
for path in sorted(Path("reports").glob("*.csv")):
print(path)
reports/2026-01.csv
reports/2026-02.csv
Only the two CSV files directly inside reports. The * matches any name, but not a folder separator, so it doesn't go into archive. On Windows, the same paths print with backslashes, like reports\2026-01.csv.
rglob: every folder below
from pathlib import Path
for path in sorted(Path("reports").rglob("*.csv")):
print(path)
reports/.venv/lib/site.csv
reports/2026-01.csv
reports/2026-02.csv
reports/archive/2025-12.csv
rglob("*.csv") is the same as glob("**/*.csv"): ** means "this folder and every folder under it". Notice two things in the result:
- **
.venv/lib/site.csvis there.**rglobgoes into every folder, including hidden ones and ones you'd never want, like.venv,.gitornode_modules. - **
2025-01.CSVis missing.** On Linux, matching is case-sensitive, so*.csvdoesn't match.CSV. On Windows it does match. Same code, different result.
⚠ Warning: Because of that, a script that finds 5 files on your Windows laptop can find 4 on a Linux server. If upper-case extensions are possible, don't rely on the pattern for it. Filter by suffix yourself, as below.
Filter what the pattern can't
from pathlib import Path
SKIP = {".venv", ".git", "node_modules", "__pycache__"}
found = [
path
for path in Path("reports").rglob("*")
if path.is_file()
and path.suffix.lower() == ".csv"
and not SKIP.intersection(path.parts)
]
for path in sorted(found):
print(path)
reports/2026-01.csv
reports/2026-02.csv
reports/archive/2025-12.csv
reports/archive/old/2025-01.CSV
rglob("*") lists everything, folders too, so is_file() keeps only files. path.suffix.lower() handles .CSV. path.parts is the path split into pieces, like ('reports', '.venv', 'lib', 'site.csv'), so the skip check works at any depth.
This still walks into .venv and then throws the results away. For a few thousand files that doesn't matter. For a folder with hundreds of thousands, use os.walk, which lets you remove folders from the list before it goes into them. That's one of the few cases where the old tool is the better one.
Sort the results
glob and rglob return files in whatever order the file system gives them. That order is different on Windows, macOS and Linux, and can even change on the same computer. If the order matters, like processing monthly reports from oldest to newest, always wrap it in sorted().
Sorting by name works for names like 2026-01.csv. If you need the newest file by date changed instead, sort by modification time:
import os
from pathlib import Path
for number, path in enumerate(sorted(Path("reports").glob("*.csv"))):
os.utime(path, (1_790_000_000 + number, 1_790_000_000 + number))
newest = max(Path("reports").glob("*.csv"), key=lambda path: path.stat().st_mtime)
print(newest.name)
2026-02.csv
The os.utime lines only set the dates for this example. In your project, the dates are already there.
Use what a Path gives you
Every result is a Path, so you don't have to cut up strings:
from pathlib import Path
path = Path("reports/archive/2025-12.csv")
print(path.name)
print(path.stem)
print(path.suffix)
print(path.parent)
print(path.relative_to("reports"))
print(path.with_suffix(".json"))
2025-12.csv
2025-12
.csv
reports/archive
archive/2025-12.csv
reports/archive/2025-12.json
with_suffix() is handy for output files: read 2025-12.csv, write 2025-12.json next to it, without string tricks that break on names like my.report.csv.
Before you delete or move anything
Finding files is harmless. Acting on them isn't. Print the list first and check it:
from pathlib import Path
targets = sorted(Path("reports/archive").rglob("*.csv"))
print(f"Would delete {len(targets)} file(s):")
for path in targets:
print(" ", path)
# for path in targets:
# path.unlink()
Would delete 1 file(s):
reports/archive/2025-12.csv
Notice it found one file, not two. 2025-01.CSV was missed again, because of the case rule above. A dry run is how you catch that before it matters.
⚠ Warning: Run the dry version first, every time, and only then uncomment the line that deletes. A pattern that's one character off, like rglob("*") instead of rglob("*.tmp"), deletes everything under the folder. Files deleted with unlink() don't go to the Recycle Bin or Trash.
Common mistakes
- **Expecting
glob("*.csv")to search subfolders.** Userglobor**. - Relying on the order. Use
sorted(). - Relying on case.
*.csvmisses.CSVon Linux. Comparesuffix.lower(). - Searching from the wrong folder.
Path("reports")is relative to where you ran the script, not where the script is. UsePath(__file__).parent / "reports"if it should be next to the script.
What happens if you skip the checks
The script works on your laptop. On the server it misses half the files because of upper-case extensions, processes them in a different order, or picks up test data from a folder you forgot about. No error. Just a report that doesn't match.
If your rglob runs from the wrong folder tomorrow, how many files will it find, and what will your script do to them?
About
glob and rglob, case sensitivity on Linux vs Windows, skipping folders, sorting, the newest file, Path parts, and a dry run before deleting. After this, you can find exactly the files you mean on any system.
- Responsible
- Mary Q
- Last Update
- 09/20/2026
- Reading Time
- 6 min
Reviews
Sign in to write a reviewComments (0)
Sign in to commentNo comments yet.