fnmatch仅匹配文件名,不能搜索文件内容;需先用fnmatch或pathlib筛选文件,再逐个读取内容匹配,并做好编码、二进制检测和异常处理。

fnmatch能匹配文件名,但不能搜索文件内容
很多人误以为 fnmatch 可以搜文本内容,其实它只做文件名模式匹配——类似 shell 的 * 和 ?。比如 fnmatch.filter(os.listdir(), "*.py") 能找出所有 Python 文件,但不会打开它们看里面有没有 "def main"。
真正要“检索包含关键词的文件”,得两步走:先用 fnmatch(或 pathlib.Path.glob)缩小候选范围,再逐个读文件内容匹配。否则遍历整个目录树却对每个文件都全文扫描,效率极低,尤其遇到二进制文件或大日志时容易卡住甚至崩溃。
用 pathlib + fnmatch 精准筛选目标文件类型
比起 os.walk + 手动字符串匹配,pathlib 更直观,也方便和 fnmatch 协同。关键是别让 fnmatch 去处理路径层级——它只适合匹配单个文件名部分。
- 正确做法:
path.name传给fnmatch.fnmatch(path.name, pattern),不是path.as_posix() - 推荐组合:
Path("src").rglob("*")遍历,再用fnmatch过滤后缀或命名规则,比如fnmatch.fnmatch(path.name, "test_*.py") - 注意大小写:
fnmatch在 Windows 默认不区分大小写,Linux/macOS 区分——若需跨平台一致,建议统一转小写再比,如fnmatch.fnmatch(path.name.lower(), "*.md")
搜索文件内容时必须加编码与异常防护
直接 open(f).read() 是最常见翻车点:遇到非 UTF-8 编码(如 GBK 日志、ISO-8859-1 配置)、空文件、权限拒绝、符号链接循环,程序立刻抛错中断。
立即学习“Python免费学习笔记(深入)”;
- 强制指定编码并容错:
open(f, encoding="utf-8", errors="ignore")——errors="ignore"会跳过无法解码的字节,比"replace"更安全(避免把乱码当关键词命中) - 加
try/except UnicodeDecodeError比只靠errors参数更可控,尤其想记录哪些文件跳过了 - 大文件别用
.read():用for line in f:逐行扫,配合line.find(keyword) != -1,内存友好且能提前退出 - 跳过二进制文件:检查前几个字节是否含
\x00(空字节),有则大概率是二进制,直接continue
完整轻量示例:找 ./src 下所有 .py 文件里含 "TODO" 的行
from pathlib import Path
import fnmatch
<p>keyword = "TODO"
root = Path("src")
patterns = ["<em>.py", "</em>.md"]</p><p>for path in root.rglob("*"):
if not path.is_file():
continue
if not any(fnmatch.fnmatch(path.name, pat) for pat in patterns):
continue</p><pre class="brush:php;toolbar:false;">try:
# 快速跳过明显二进制
if path.stat().st_size == 0:
continue
with open(path, "rb") as f:
if b"\x00" in f.read(1024):
continue
# 安全读文本
with open(path, encoding="utf-8", errors="ignore") as f:
for i, line in enumerate(f, 1):
if keyword in line:
print(f"{path}:{i} {line.rstrip()}")
except (OSError, UnicodeError):
pass
这个逻辑没用第三方库,启动快、依赖少,适合集成进 CI 脚本或 pre-commit 钩子。真正麻烦的不是写法,而是忽略二进制检测或编码错误处理——这两个点一漏,脚本在别人机器上十有八九跑挂。


















