Last active
September 22, 2025 09:51
-
-
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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