Skip to Content
Finding Files in Python With pathlib glob and rglob
Intermediate
0 likes
Tutorials

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:

  1. **.venv/lib/site.csv is there.** rglob goes into every folder, including hidden ones and ones you'd never want, like .venv, .git or node_modules.
  2. **2025-01.CSV is missing.** On Linux, matching is case-sensitive, so *.csv doesn'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.** Use rglob or **.
  • Relying on the order. Use sorted().
  • Relying on case. *.csv misses .CSV on Linux. Compare suffix.lower().
  • Searching from the wrong folder. Path("reports") is relative to where you ran the script, not where the script is. Use Path(__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
Was this helpful?
​ 0 ​ 0

Comments (0)

Sign in to comment

No comments yet.