【问题标题】:Exclude subfolders with glob使用 glob 排除子文件夹
【发布时间】:2019-12-26 17:01:36
【问题描述】:

我正在尝试使用 glob.iglob 在特定文件夹的所有子文件夹中搜索 xml 文件。问题是链接了一些文件夹,我进入了某种永无止境的子文件夹路径。例如:

First Level\
 Second Level A\
  Third Level: Link to Second Level B\
  Third Level: subfolder with xml files\
 Second Level B\
  Third Level: Link to Second Level A\
  Third Level: subfolder with xml files\

所以我需要用他们的名字排除一些子文件夹。有没有办法这样做? 我已经尝试过传递如下列表:

glob.iglob([r'/**/*.xml', r'!/Link to Second Level B/'])

但这对我不起作用。

有人知道如何解决这个问题吗?

感谢您的帮助!

【问题讨论】:

    标签: python file recursion directory glob


    【解决方案1】:

    我想首先指出这种(重复出现的)符号链接是糟糕设计的标志。任何解决方法都是解决问题的结果而不是原因(“扫除地毯下的污垢”)。

    不幸的是,(重复出现的)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 上运行了部分代码,所以我预计不会出现任何意外

    【讨论】:

    • 不幸的是,我无法更改此符号链接。同时,我将程序更改为仅在没有符号链接的子文件夹中搜索,但现在我将尝试您的解决方案。非常感谢您的工作!
    • 没有人说要更改 symlink。这就是我发布代码的原因:)。搜索每个目录也可以,但是如果有很多子目录,并且 symlink 位于树中较低的某个位置,则它将不起作用。
    • @Meret:这回答了你的问题吗?如果是,请接受答案,以便其他人也能承认([SO]: What should I do when someone answers my question?)。