Skip to content

Instantly share code, notes, and snippets.

@b0tting
Last active September 22, 2025 09:51
Show Gist options
  • Select an option

  • Save b0tting/98001d5ac6d18a12d3a1da3ae0090e4d to your computer and use it in GitHub Desktop.

Select an option

Save b0tting/98001d5ac6d18a12d3a1da3ae0090e4d to your computer and use it in GitHub Desktop.
Python os.walk() alternative that treats windows junctions as symlinks and will not iterate into them.
import os
import stat
def is_reparse_point(path: str) -> bool:
"""
Return True if the given path is a Windows reparse point (junction, symlink, mount point, etc.).
On non-Windows, just use os.path.islink.
"""
try:
st = os.lstat(path)
except OSError:
return False
# On Windows, check reparse attribute
if hasattr(st, "st_file_attributes"):
return bool(st.st_file_attributes & stat.FILE_ATTRIBUTE_REPARSE_POINT)
# On POSIX, treat symlinks as "reparse points"
return os.path.islink(path)
def winwalk(top, topdown=True, onerror=None, followlinks=False):
"""
os.walk replacement for Windows that does not recurse into reparse points.
Windows has the concept of junctions, which are similar to symlinks but
are implemented differently. The built-in os.walk with followlinks=True
will recurse into junctions, which can lead to infinite recursion if
there are circular references. This function avoids that by checking for
reparse points and not recursing into them.
This function yields a 3-tuple (dirpath, dirnames, filenames) just like os.walk.
Parameters:
- top: The root directory to start walking from.
- topdown: If True, the directories are generated from the top down.
- onerror: A function that gets called with an OSError instance if an error occurs
while listing directories.
- followlinks: If True, symbolic links are followed. This is passed to os.walk
when followlinks is True, but reparse points are still not recursed into.
Example usage:
for dirpath, dirnames, filenames in winwalk('C:\\mydir'):
print(dirpath, dirnames, filenames)
"""
if followlinks:
return os.walk(top, topdown=topdown, onerror=onerror)
try:
names = os.listdir(top)
except OSError as err:
if onerror is not None:
onerror(err)
return
dirs, nondirs = [], []
for name in names:
fullpath = os.path.join(top, name)
try:
if os.path.isdir(fullpath) and not is_reparse_point(fullpath):
dirs.append(name)
else:
nondirs.append(name)
except OSError:
nondirs.append(name)
if topdown:
yield top, dirs, nondirs
for d in dirs:
new_path = os.path.join(top, d)
yield from winwalk(new_path, topdown, onerror)
if not topdown:
yield top, dirs, nondirs
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment