Desk Clarity

Everyday document & browser help

Pathlib Glob Finds Hidden Files: Compare the Whole Relative Path

By ON Lab · Sources checked November 8, 2026

Switching from glob.glob("*") to Path.glob("*") can change the selected files even when the pattern looks identical. Dot-prefixed names are ordinary matches for pathlib. Decide whether hidden names belong in your input set, then compare complete relative paths before running a copy, upload or build operation.

Reproduce selection without acting on it

A migration may look like a return-type change from strings to Path objects. It also changes matching behavior. Make a separate synthetic directory so you can inspect the difference without exposing project secrets. Here is the complete fixture we used:

sample/
  visible.txt
  .hidden.txt
  .private/
    inside.txt
from pathlib import Path
import glob
root = Path("sample")
print(sorted(Path(p).name for p in glob.glob(str(root / "*"))))
print(sorted(p.name for p in root.glob("*")))

On Python 3.9.6, the first result was ["visible.txt"]. The pathlib result was [".hidden.txt", ".private", "visible.txt"]. Both files and directories can appear, so a file-only pipeline needs an explicit type decision. Sorting makes this comparison readable; neither snippet assumes a stable filesystem enumeration order.

Recursive filtering needs the relative path

Checking only p.name excludes a hidden file but still allows an ordinary filename inside a hidden directory. In our fixture, .private/inside.txt has the basename inside.txt. For the policy “exclude every path containing a dot-prefixed component below this root,” inspect all relative components:

from pathlib import Path
root = Path("sample")
selected = []
for path in root.rglob("*"):
    relative = path.relative_to(root)
    if any(part.startswith(".") for part in relative.parts):
        continue
    if path.is_file():
        selected.append(relative.as_posix())
print(sorted(selected))

The result was ["visible.txt"]. Using the relative path is deliberate: a user-selected root might itself be inside a dot-prefixed directory, and this policy applies below that root. If your rule also excludes its ancestors, make that a separate explicit decision.

Dot-prefixed is not a universal hidden flag

This example defines hidden by a leading dot. It does not inspect Windows hidden attributes or Finder metadata. It is a selection example, not a complete safe-upload implementation. is_file() can follow a symbolic link to its target. A workflow that must stay within one tree needs its own link and boundary policy before reading selected content.

Current Python documentation lists further interface differences, including path shape and recursive behavior. Python 3.11 added include_hidden to the glob module. Do not paste that option into Python 3.9 and expect it to work. When upgrading, use your interpreter’s documentation and run the fixture before changing production selection.

Audit paths, not just the count

old = set(old_relative_paths)
new = set(new_relative_paths)
print("added", sorted(new - old))
print("removed", sorted(old - new))

Populate these variables from the two selectors before performing writes. Equal counts do not establish equal files: one selector can omit a visible input and include a hidden one. Store a reviewed path list for the actual operation. A small fixture demonstrates the rule; a complete manifest shows what your real operation would touch.

Desired resultUseful approach
Preserve the previous input setKeep the old selector until its replacement produces identical reviewed paths.
Include configuration deliberatelyUse an explicit allowlist of intended names and directories.
Exclude dot-prefixed subtreesCheck all relative components, not only the leaf name.
Select by OS hidden attributesUse a platform-specific rule and test it on that platform.

If a file is missing from Git but present in Python’s result, inspect Git’s actual ignore-rule source. Git ignore rules do not automatically control Python traversal. If a selected file cannot be opened, investigate permissions or concurrent changes; matching a path does not establish that it remains readable.

Method: current versioned Python documentation and a CPython report comparing glob interfaces, plus an original four-path fixture executed with Python 3.9.6 on macOS. No Windows hidden attributes, link policies or every recursive-version difference were reproduced.

Sources