fnmatch不能直接匹配多级目录,因为它只进行字符串匹配而不解析路径结构,默认不匹配/,且不支持*语法;fnmatch.fnmatch()大小写不敏感(系统依赖),fnmatch.fnmatchcase()严格区分大小写。

fnmatch 为什么不能直接匹配路径中的多级目录?
因为 fnmatch 只做字符串模式匹配,不解析路径结构。它把整个路径字符串(比如 "logs/2024-01/error.log")当作一个普通字符串处理,不会自动按 / 拆分或递归判断每级目录是否符合模式。所以 fnmatch.fnmatch("logs/2024-01/error.log", "*/error.*") 返回 False —— 因为 * 默认不匹配 / 字符。
实际使用中常见错误是误以为 ** 或通配符能跨目录生效,但 fnmatch 完全不支持 **(那是 glob 或 pathlib.Path.rglob() 的语法)。
- 若需匹配含斜杠的路径,必须显式在模式中写
/,例如"logs/*/error.*" - 想让
*匹配/,得自己预处理:用replace('/', '!')统一替换后再匹配(不推荐,破坏语义) - 更合理的做法是结合
os.walk()或pathlib.Path.glob(),让它们负责遍历,再用fnmatch做单层文件名过滤
fnmatch.fnmatch() 和 fnmatch.fnmatchcase() 有什么实质区别?
区别只在大小写敏感性,且仅影响 ASCII 字母。在默认 locale 下,fnmatch.fnmatch("Readme.md", "readme.*") 在 macOS/Linux 返回 True,但在 Windows 可能返回 False —— 因为 Windows 的 fnmatch 实现受系统 API 影响,行为不一致。
-
fnmatch.fnmatch():按当前系统规则判断大小写,不可靠 -
fnmatch.fnmatchcase():严格区分大小写,"Readme.md"不会匹配"readme.*" - 跨平台脚本中,如果逻辑依赖大小写(如区分
Config.py和config.py),务必用fnmatchcase - 注意:Unicode 字符(如中文、带重音符号的字母)不受这两个函数大小写逻辑影响,它们始终逐字符比对
如何安全地用 fnmatch 过滤 os.listdir() 的结果?
直接对 os.listdir() 返回的文件名列表调用 fnmatch 是常见做法,但容易忽略两点:路径拼接和隐藏文件。
立即学习“Python免费学习笔记(深入)”;
- 先用
os.path.join(dir_path, name)构造完整路径,再用os.path.isfile()或os.path.isdir()判断类型,避免把子目录名当文件匹配 - Linux/macOS 下以
.开头的文件(如.gitignore)默认被 shell 隐藏,但os.listdir()会返回它们;若不想匹配隐藏文件,得额外加条件not name.startswith('.') - 示例片段:
import os, fnmatch files = [f for f in os.listdir("/var/log") if not f.startswith('.') and fnmatch.fnmatch(f, "*.log")] - 不要在模式里写绝对路径(如
"/var/log/*.log"),fnmatch会把它当纯字符串比对,而os.listdir()只返回 basename
fnmatch 的性能瓶颈在哪?什么时候该换方案?
单次匹配很快,但循环大量文件时,反复调用 fnmatch 会因 Python 解释器开销和正则引擎(内部将 glob 转为正则)拖慢速度。尤其当模式含多个 ? 或嵌套 *(如 "a*b*c*d*.txt"),回溯成本明显上升。
- 匹配几千个文件且模式简单(如
"*.py"):fnmatch完全够用 - 需频繁匹配上万文件,或模式复杂:改用
pathlib.Path.glob("*.py")(底层调用系统 glob,C 实现)或预编译正则(re.compile(r".*.py$")) - 注意:
glob.glob()和pathlib.Path.glob()内部也用到了fnmatch,只是封装了路径遍历逻辑;真正绕过fnmatch的是直接用re+os.listdir() - 调试时可用
fnmatch.translate(pattern)查看它生成的正则,比如fnmatch.translate("*.log")返回'.*\.log\Z(?ms)',便于理解匹配边界
真正容易被忽略的是 locale 设置:某些非 UTF-8 locale 下,fnmatch 对非 ASCII 字符的匹配可能异常,生产环境建议固定 LC_ALL=C 或统一用 pathlib + str.endswith() 替代简单后缀匹配。


















