我想首先指出这种(重复出现的)符号链接是糟糕设计的标志。任何解决方法都是解决问题的结果而不是原因(“扫除地毯下的污垢”)。
不幸的是,(重复出现的)glob 不允许过滤,也不能在枚举元素时提供对元素的访问。因此,您需要另一种方法,通过自己枚举 dir 元素(使用许多现有方法中的一种 - 您可以查看 [SO]: How do I check whether a file exists without exceptions? (@CristiFati's answer))并过滤掉不需要的元素。
这是测试 dir 结构。请注意,这里的 2 个重复出现的 symlink 实际上是正常的 dir,否则它们会弄乱命令(它也不处理这种情况)。之后我用 symlink 替换了它们:
[cfati@CFATI-5510-0:e:\Work\Dev\*\q057591233]> tree /a /f .
Folder PATH listing for volume Work
Volume serial number is 3655-6FED
E:\WORK\DEV\*\Q057591233
| code00.py
|
+---external_dir
| file00.xml
|
\---search_dir
| file00.xml
| file01.xml
|
+---dir00
| +---dir00
| | | file00.xml
| | |
| | \---dir00
| | file00.xml
| |
| \---dir01_symlink_to_parent_sibbling_dir01
\---dir01
+---dir00_symlink_to_parent_sibbling_dir00
+---dir01
| file00.xml
|
\---dir02_symlink_to_external_dir
file00_ext.xml
code00.py:
#!/usr/bin/env python3
import sys
import os
import re
import pprint
def _get_files_os_scandir_no_symlikns(dir_name, match_func, level=0):
for item in os.scandir(dir_name):
if item.is_symlink():
continue
if item.is_dir():
yield from _get_files_os_scandir_no_symlikns(item.path, match_func, level=level + 1)
elif match_func(item.path):
yield item.path
def _get_files_os_scandir(dir_name, match_func, visited_inodes, level=0):
for item in os.scandir(dir_name):
if item.inode() in visited_inodes:
continue
visited_inodes.append(item.inode())
item_path = os.path.normpath(os.path.join(*os.path.split(item.path)[:-1], os.readlink(item.path))) if item.is_symlink() else item.path
if item.is_dir():
yield from _get_files_os_scandir(item_path, match_func, visited_inodes, level=level + 1)
elif match_func(item_path):
yield item_path
def get_files(path, ext, exclude_symlinks=True):
if exclude_symlinks and os.path.islink(path):
return
pattern = re.compile(".*\.{0:s}$".format(ext))
if os.path.isdir(path):
if exclude_symlinks:
yield from _get_files_os_scandir_no_symlikns(path, pattern.match)
else:
yield from _get_files_os_scandir(path, pattern.match, list())
elif os.path.isfile(path) and pattern.match(path):
yield path
def main():
search_dir = "search_dir"
extension = "xml"
for exclude_symlinks in [True, False]:
print("\nExclude symlinks: {0:}".format(exclude_symlinks))
files = list(get_files(search_dir, extension, exclude_symlinks=exclude_symlinks))
pprint.pprint(files)
print("Total items: {0:d}".format(len(files)))
if __name__ == "__main__":
print("Python {0:s} {1:d}bit on {2:s}\n".format(" ".join(item.strip() for item in sys.version.split("\n")), 64 if sys.maxsize > 0x100000000 else 32, sys.platform))
main()
print("\nDone.")
输出:
[cfati@CFATI-5510-0:e:\Work\Dev\*\q057591233]> "e:\Work\Dev\VEnvs\py_064_03.07.03_test0\Scripts\python.exe" code00.py
Python 3.7.3 (v3.7.3:ef4ec6ed12, Mar 25 2019, 22:22:05) [MSC v.1916 64 bit (AMD64)] 64bit on win32
Exclude symlinks: True
['search_dir\\dir00\\dir00\\dir00\\file00.xml',
'search_dir\\dir00\\dir00\\file00.xml',
'search_dir\\dir01\\dir01\\file00.xml',
'search_dir\\file00.xml',
'search_dir\\file01.xml']
Total items: 5
Exclude symlinks: False
['search_dir\\dir00\\dir00\\dir00\\file00.xml',
'search_dir\\dir00\\dir00\\file00.xml',
'search_dir\\dir01\\dir01\\file00.xml',
'external_dir\\file00_ext.xml',
'search_dir\\file00.xml',
'search_dir\\file01.xml']
Total items: 6
Done.
注意事项:
- 递归实现依赖于[Python 3.Docs]: os.scandir(path='.')(和其他文件/dir函数)
- 在文件名匹配方面,不支持通配符,因此使用最接近 (?) 的东西 (regexp)
- 遍历 dir 的 2 个函数:
-
_get_files_os_scandir_no_symlikns - 忽略所有 symlinks
-
_get_files_os_scandir - 包括符号链接。还进行了一些处理以避免无限递归和符号链接分辨率
- 这 2 个函数本来可以统一(带有一个额外的参数 (exclude_symlinks)),但我感觉忽略它们的函数以这种方式执行得更快
- 正如所见,没有一个进入无限递归(对于前者很明显),但前者也省略了搜索之外的文件 dir
-
get_files_os_scandir - 一个包装器,在完成一些初始化工作后调用 2 中的任何一个(以避免每次重复调用都这样做)
- 我只在 Win 上运行了代码,但我也在 Nix 上运行了部分代码,所以我预计不会出现任何意外