当前位置: 代码网 > it编程>前端脚本>Python > 深度解析Python中glob模块模式匹配的规则与常见坑

深度解析Python中glob模块模式匹配的规则与常见坑

2026年10月11日 • Python •我要评论
在 python 中处理文件系统时,glob 模块是搜索文件的首选工具。它让你用简洁的通配符模式(如 *.txt、data/**/*.csv)轻松定位文件,不必手写复杂的目录遍历逻辑。然而,很多开发者

在 python 中处理文件系统时,glob 模块是搜索文件的首选工具。它让你用简洁的通配符模式(如 *.txt、data/**/*.csv)轻松定位文件,不必手写复杂的目录遍历逻辑。然而,很多开发者在使用 glob 时都会遇到令人困惑的现象:明明存在的文件却搜索不到;在 windows 上工作正常的模式,到了 linux 上结果完全不同;递归匹配时返回了意料之外的嵌套目录;或者搜索速度随着目录层级增加而急剧下降。更隐蔽的是,glob 返回的顺序没有保证,依赖它做后续处理可能在不同运行中产生不一致的结果。

这些问题的根源,在于 glob 的匹配规则并非通用正则表达式,而是遵循 unix shell 的文件名扩展约定,且其行为深受操作系统、文件系统隐藏文件规则以及递归实现细节的影响。今天,我们就来彻底解剖 glob 模块的匹配机制,看透那些让你“找不到文件”或“搜错文件”的陷阱,并掌握从简单模式到复杂递归搜索的正确姿势。

一、问题复现:那些“找不到”与“搜错了”的瞬间

场景 1:*.txt匹配不到.txt隐藏文件

import glob
import os

os.makedirs('test', exist_ok=true)
open('test/.hidden.txt', 'w').close()
open('test/visible.txt', 'w').close()

print(glob.glob('test/*.txt'))
# 输出:['test/visible.txt'],.hidden.txt 被忽略了

在 unix 系统中,以 . 开头的文件被视为隐藏文件,* 通配符默认不匹配它们。这是 shell 的传统行为,glob 也继承了这个规则。如果你需要匹配隐藏文件,必须显式使用 .* 模式,例如 test/.*.txt,但这同时会匹配 . 和 ..,需要额外过滤。

场景 2:递归**在旧版 python 中不生效

import glob

# 需要 recursive=true 才生效
files = glob.glob('**/*.py')   # 在 python 3.5+ 中,不加 recursive=true 只匹配当前目录

很多开发者以为 ** 会自动递归,但忘记了 recursive=true 参数。在 python 3.5 之前,** 甚至不被支持。即使加了参数,** 匹配的深度也取决于实现。

场景 3:windows 与 linux 路径分隔符差异

import glob

# 在 windows 上,路径分隔符是反斜杠
files = glob.glob('c:\\data\\*.txt')   # 可行,但容易因转义出错
files = glob.glob('c:/data/*.txt')     # 在 windows 上通常也可以

# 在 linux 上
files = glob.glob('/data/*.txt')

如果你在代码中硬编码了平台特定的分隔符,跨平台时就会失败。更安全的方式是使用 os.path.join 或 pathlib 来构造模式。

场景 4:**递归匹配时包含了目录本身

import glob
import os

os.makedirs('a/b/c', exist_ok=true)
open('a/b/c/file.txt', 'w').close()

print(glob.glob('a/**', recursive=true))
# 输出包含 'a/', 'a/b/', 'a/b/c/', 'a/b/c/file.txt'

** 不仅匹配文件,还会匹配目录。如果你只想要文件,需要在结果中过滤 os.path.isfile()。

场景 5:返回顺序不确定,依赖顺序的代码出现随机 bug

files = glob.glob('*.txt')
# 文件的返回顺序取决于文件系统的目录项顺序,而非字母顺序

在不同机器、不同文件系统、甚至同一机器的不同运行中,顺序可能不同。如果你的代码依赖于“第一个文件”或“按顺序处理”,就会产生不一致的结果。应显式排序。

场景 6:模式中的特殊字符未转义

import glob

# 文件名包含方括号
files = glob.glob('data[1].txt')   # 方括号被解释为字符集,而不是字面量

如果要匹配字面意义上的 [ 或 ],需要使用 glob.escape() 或 glob.escape 函数。

场景 7:glob不匹配目录下的符号链接目标

import glob
import os

os.symlink('/target/dir', 'link_dir')
print(glob.glob('link_dir/*.txt'))   # 取决于操作系统和实现,可能不进入符号链接

glob 默认是否跟随符号链接在递归模式下由 include_hidden 等参数影响,且平台行为不一致。

二、底层原理:glob的匹配规则与实现

1. 通配符语法

glob 支持四种通配符,与 unix shell 的规则一致:

通配符含义
*匹配任意数量的字符(包括零个),但不匹配路径分隔符 /,也不匹配以 . 开头的文件(除非模式以 . 开头)
?匹配任意单个字符,不匹配路径分隔符
[seq]匹配 seq 中的任意一个字符。支持范围如 [a-z],也支持 [!seq] 排除
**递归匹配任意层级的目录,仅在 recursive=true 时有效

2. 隐藏文件的特殊规则

在 unix 传统中,* 不匹配以 . 开头的文件。因此 glob('*') 会忽略 .bashrc、.git 等。若要匹配隐藏文件,必须显式以 . 开头,如 glob('.*')。但 .* 也会匹配 . 和 ..,通常需要过滤。

3. 递归匹配的实现

当使用 ** 且 recursive=true 时,glob 会遍历目录树。** 匹配任意数量的目录层级(包括零层),因此 **/*.py 会匹配当前目录及所有子目录中的 .py 文件。

4.glob与fnmatch的关系

glob 的匹配逻辑基于 fnmatch 模块,后者实现了 unix shell 风格的通配符匹配。但 glob 在 fnmatch 基础上增加了路径分隔符的处理和目录遍历。

5.iglob与glob的区别

  • glob.glob() 返回一个列表,一次性加载所有结果。
  • glob.iglob() 返回一个迭代器,按需生成结果,内存效率更高,适合处理大量文件。

6.pathlib的glob

pathlib.path 提供了面向对象的 glob 方法,语法更现代:

from pathlib import path

p = path('.')
for f in p.glob('**/*.py'):
    print(f)

注意:pathlib 的 glob 在 python 3.5+ 中自动支持 **,不需要 recursive=true 参数。

7. 返回结果的顺序

glob 不保证返回顺序。它依赖于底层 os.scandir() 或 os.listdir() 的顺序,而后者取决于文件系统的目录项排列。因此,需要排序时请显式使用 sorted()。

三、常见陷阱与错误模式

陷阱 1:忘记recursive=true

glob.glob('**/*.py')   # 只匹配当前目录的 .py 文件,不递归
glob.glob('**/*.py', recursive=true)   # 正确,递归所有子目录

陷阱 2:**匹配到目录

files = glob.glob('**/*', recursive=true)
# 包含了目录
files = [f for f in files if os.path.isfile(f)]

陷阱 3:模式中使用了错误的路径分隔符

# 在 linux 上
glob.glob('data\\*.txt')   # 不匹配,因为 \ 不是分隔符

应使用 / 或 os.sep,或使用 pathlib。

陷阱 4:依赖返回顺序

files = glob.glob('*.txt')
first = files[0]   # 可能是任意文件

始终 sorted(glob.glob(...))。

陷阱 5:模式中的特殊字符未转义

glob.glob('file[1].txt')   # 方括号被解释为字符集
glob.glob(glob.escape('file[1].txt'))   # 正确匹配字面量

陷阱 6:递归搜索不跟随符号链接导致遗漏

在某些情况下,符号链接指向的目录不会被递归进入。如果需要跟随,应使用 os.walk(followlinks=true) 或 pathlib 的 rglob 配合相应处理。

陷阱 7:在大量文件上使用glob.glob()导致内存占用过高

应使用 glob.iglob() 或 pathlib.path.rglob() 迭代处理。

陷阱 8:大小写敏感性问题

在 linux 上,文件名匹配是大小写敏感的;在 windows 和 macos(默认)上,文件系统不区分大小写。因此 glob('*.txt') 在 linux 上可能匹配不到 file.txt,而在 windows 上可以。跨平台代码应统一大小写或使用 fnmatch 的 case_sensitive 参数(python 3.13+)。

陷阱 9:**的深度限制

在某些实现中,** 递归可能因为符号链接循环而无限递归。python 的 glob 会检测并跳过循环,但如果你自己实现递归,需要小心。

四、正确解决方案:精确控制每一次匹配

1. 基本匹配

import glob

# 当前目录所有 .txt 文件
txt_files = glob.glob('*.txt')

# 指定目录
txt_files = glob.glob('/path/to/dir/*.txt')

# 匹配多种扩展名
files = glob.glob('*.txt') + glob.glob('*.md')
# 或使用字符集
files = glob.glob('*.[tm]*')   # 匹配 .txt, .md 等,但可能过宽

2. 递归匹配

# 所有 .py 文件(包括子目录)
py_files = glob.glob('**/*.py', recursive=true)

# 只匹配文件,排除目录
py_files = [f for f in glob.glob('**/*.py', recursive=true) if os.path.isfile(f)]

3. 包含隐藏文件

# 匹配所有文件,包括隐藏文件
all_files = glob.glob('*') + glob.glob('.*')
# 过滤掉 . 和 ..
all_files = [f for f in all_files if f not in ('.', '..')]

或者使用 pathlib 的 path.glob 并配合 include_hidden=true(python 3.11+):

from pathlib import path
files = list(path('.').glob('*', include_hidden=true))

4. 安全转义

import glob

pattern = glob.escape('file[1].txt')   # 转义特殊字符
files = glob.glob(pattern)

5. 排序结果

files = sorted(glob.glob('*.txt'))
# 或按修改时间排序
files = sorted(glob.glob('*.txt'), key=os.path.getmtime, reverse=true)

6. 使用pathlib的现代 api

from pathlib import path

# 递归搜索
for p in path('.').rglob('*.py'):
    print(p)

# 匹配当前目录
for p in path('.').glob('*.txt'):
    print(p)

# 包含隐藏文件(python 3.11+)
for p in path('.').glob('*', include_hidden=true):
    print(p)

7. 处理大量文件:使用iglob

import glob

for filepath in glob.iglob('**/*.log', recursive=true):
    process(filepath)

8. 跨平台路径构造

import os
import glob

base = '/data'   # 或使用 path
pattern = os.path.join(base, '*.txt')
files = glob.glob(pattern)

使用 pathlib 更简洁:

from pathlib import path

base = path('/data')
files = list(base.glob('*.txt'))

9. 匹配文件名大小写不敏感(跨平台)

import glob
import os

def case_insensitive_glob(pattern):
    # 获取目录和文件名模式
    dirname, basename = os.path.split(pattern)
    if not dirname:
        dirname = '.'
    # 使用 fnmatch 手动过滤
    import fnmatch
    results = []
    for entry in os.listdir(dirname):
        if fnmatch.fnmatch(entry.lower(), basename.lower()):
            results.append(os.path.join(dirname, entry))
    return results

python 3.13+ 的 glob.glob 支持 case_sensitive 参数(部分平台),但兼容性有限。

五、调试与验证技巧

  1. 打印模式:在调用 glob 前打印 repr(pattern),确认转义和分隔符是否正确。
  2. 使用 os.listdir() 对比:查看目录下实际有哪些文件,确认是否被隐藏或过滤。
  3. 使用 os.walk() 交叉验证:确认递归搜索是否遗漏了某些目录。
  4. 检查 glob.escape:当文件名包含特殊字符时,确保已转义。
  5. 单元测试覆盖:创建包含隐藏文件、子目录、符号链接的临时目录树,验证各种模式。
  6. 注意平台差异:在 linux 和 windows 上分别测试模式匹配行为。
  7. 性能测试:对于大目录树,比较 glob、os.walk、pathlib 的性能。

六、最佳实践总结

  • 使用 pathlib 的 glob/rglob 作为现代首选,代码更清晰,跨平台更好。
  • ** 递归时记得传 recursive=true(glob.glob/iglob),pathlib 则自动支持。
  • 隐藏文件不会被 * 匹配,需要显式处理。
  • 返回顺序不保证,需要时请排序。
  • 使用 glob.escape() 转义文件名中的特殊字符。
  • 只处理文件时,过滤 os.path.isfile()。
  • 大文件量使用 iglob() 或迭代器,避免一次性加载。
  • 避免硬编码路径分隔符,使用 os.path.join 或 pathlib。
  • 注意大小写敏感性在不同平台上的差异。
  • 对符号链接的递归行为保持警惕,必要时使用 os.walk(followlinks=true)。
  • 在安全敏感的上下文中,校验匹配结果是否在允许的目录内。

七、结语

glob 模块是文件搜索的快捷方式,但它远非万能的魔法。它遵循着 unix shell 的古老规则,对隐藏文件、路径分隔符、递归深度和返回顺序都有自己的一套逻辑。理解这些规则,你就能写出精准、可移植的搜索代码,不再为“明明有文件却搜不到”而困惑。从今天起,当你面对复杂的文件搜索需求时,先想想 glob 是否合适,如果不合适,pathlib.rglob 或 os.walk 可能是更好的选择。掌握这些工具,你的文件系统操作将如虎添翼,无论面对多么混乱的目录结构,都能快速找到你需要的文件。

以上就是深度解析python中glob模块模式匹配的规则与常见坑的详细内容,更多关于python glob匹配规则的资料请关注代码网其它相关文章!

赞 (0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2026  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com