glob 文件名匹配
当程序需要在指定目录中找到一批文件时,如何限制搜索范围、控制匹配规则,并确认结果不会误伤其他内容?glob 模块使用通配符批量匹配文件和目录名称,适合解决下面这类问题:
- 找出一个目录中的所有 Python 文件
- 递归查找所有 Markdown 文档
- 匹配具有相似名称的一组文件
glob 只负责查找路径,不读取文件内容,也不会自动删除或移动文件。
文件名匹配规则
通配符和正则表达式都能描述匹配规则,但它们不是同一种语法:
| 工具 | 主要匹配对象 | 示例 |
|---|---|---|
glob | 文件和目录名称 | *.py |
| 正则表达式 | 普通字符串内容 | r"[A-Za-z_]+\.py" |
glob 中的 * 不是正则中的 .*,也不能在 glob 规则中使用 \d、+、() 等正则语法。
常用通配符:
| 通配符 | 含义 |
|---|---|
* | 匹配一个路径片段中的任意数量字符 |
? | 匹配一个路径片段中的任意单个字符 |
[abc] | 匹配集合中的一个字符 |
[a-z] | 匹配范围中的一个字符 |
[!abc] | 匹配不在集合中的一个字符 |
** | 在递归模式下跨越多层目录 |
例如:
| 规则 | 可能匹配的名称 |
|---|---|
*.py | main.py、student.py |
test_?.py | test_a.py、test_1.py |
[ab]*.log | app.log、backup.log |
[!a]*.txt | 不以 a 开头的 .txt 文件 |
普通的 * 和 ? 不会跨越路径分隔符。需要进入多层目录时,应使用递归规则 **。
搜索范围与结果管理
glob.glob(pattern) 返回所有匹配路径组成的列表:
import glob
python_files = glob.glob("*.py")
print(python_files)相对规则默认从当前工作目录开始匹配,因此程序从不同目录启动时,结果可能不同。
文件系统没有保证返回顺序。需要稳定展示或处理时,应主动排序:
python_files = sorted(glob.glob("*.py"))匹配结果可能包含文件,也可能包含目录。后续操作要求特定类型时,需要配合 Path.is_file() 或 Path.is_dir() 检查。
固定搜索范围:
Python 3.14 可以通过 root_dir 指定搜索根目录,不需要调用 os.chdir():
from pathlib import Path
import glob
script_dir = Path(__file__).resolve().parent
sample_dir = script_dir / "sample_files"
python_files = sorted(glob.glob("*.py", root_dir=sample_dir))
print(python_files)规则是相对路径时,返回结果也相对于 root_dir。搜索过程不会改变程序的当前工作目录。
递归查找:
** 配合 recursive=True 可以进入多层目录:
markdown_files = sorted(
glob.glob("**/*.md", root_dir=sample_dir, recursive=True)
)
print(markdown_files)**/*.md 会匹配搜索根目录及其子目录中的 Markdown 文件。
递归扫描大型目录可能比较慢。应该把 root_dir 限制在明确范围内,不要轻易从用户主目录或磁盘根目录开始递归。
延迟读取结果:iglob()
glob.glob() 会先收集全部结果并返回列表。glob.iglob() 返回迭代器,可以逐个取得结果:
for path in glob.iglob("**/*.py", root_dir=sample_dir, recursive=True):
print(path)结果较多,或者只需要逐项处理时,可以使用 iglob()。如果展示顺序必须稳定,仍然需要收集结果后排序。
特殊名称与隐藏内容
glob 默认不会让普通通配符匹配以点开头的隐藏名称。例如,*.py 不会匹配 .hidden.py,递归查找也不会自动进入 .hidden 目录。
如果明确需要包含隐藏内容,可以设置 include_hidden=True:
all_python_files = sorted(
glob.glob(
"**/*.py",
root_dir=sample_dir,
recursive=True,
include_hidden=True,
)
)也可以在规则中明确写出点号,例如 .hidden/*.py。是否处理隐藏文件应该是主动选择,而不是无意扩大扫描范围。
按字面匹配特殊文件名:
文件名本身可能包含 *、? 或 [。如果希望按字面匹配这些字符,可以使用 glob.escape():
special_name = "[draft].txt"
escaped_pattern = glob.escape(special_name)
result = glob.glob(escaped_pattern, root_dir=sample_dir)
print(result)如果直接把 [draft].txt 当作规则,方括号会被解释为字符集合,不会表示完整的字面文件名。
与 pathlib 配合
glob 模块返回字符串路径:
paths = glob.glob("**/*.py", root_dir=sample_dir, recursive=True)如果已经使用 Path 管理路径,也可以直接调用:
python_files = sorted(sample_dir.glob("*.py"))
all_python_files = sorted(sample_dir.rglob("*.py"))| 写法 | 返回结果 | 搜索范围 |
|---|---|---|
glob.glob() | 字符串列表 | 由规则和 root_dir 决定 |
glob.iglob() | 字符串迭代器 | 由规则和 root_dir 决定 |
Path.glob() | Path 对象迭代器 | 当前路径下按规则匹配 |
Path.rglob() | Path 对象迭代器 | 当前路径下递归匹配 |
隐藏文件行为也有区别:glob 默认忽略点号开头的名称,而 Path.glob()、Path.rglob() 会把点号当作普通字符。
案例:查找课程文件
需求: 在固定的 sample_files 目录中查找 Markdown 文件、可见与隐藏的 Python 文件,以及名称包含特殊字符的文本文件,并只输出匹配路径。
功能分析: 使用 root_dir 限制搜索范围,用 ** 和 recursive=True 查找子目录,分别用默认规则与 include_hidden=True 对比隐藏文件,再用 iglob() 和 glob.escape() 处理不同结果形式与特殊文件名。
准备下面的示例目录:
01-find-course-files/
├── main.py
└── sample_files/
├── .hidden/
│ └── secret.py
├── docs/
│ └── intro.md
├── tests/
│ └── test_app.py
├── [draft].txt
├── README.md
└── app.py按目录树创建样本文件。隐藏目录 .hidden 和文件名 [draft].txt 要保持原样,才能观察两种匹配规则的差异。
# 示例课程
用于 glob 文件匹配练习。# 课程说明
glob 可以按照通配符查找文件。文件名中包含方括号。样本中的三个 Python 文件各写入一行,程序只匹配它们的路径,不执行其内容:
print("示例程序")print("示例测试文件")print("隐藏目录中的示例")程序固定搜索 sample_files,分别查找文档、可见 Python 文件、全部 Python 文件和特殊文件名:
from pathlib import Path
import glob
def show_paths(title, paths):
print(f"{title}:")
for path in paths:
print(f"- {path}")
def main():
script_dir = Path(__file__).resolve().parent
sample_dir = script_dir / "sample_files"
markdown_files = sorted(
glob.glob("**/*.md", root_dir=sample_dir, recursive=True)
)
visible_python_files = sorted(
glob.iglob("**/*.py", root_dir=sample_dir, recursive=True)
)
all_python_files = sorted(
glob.glob(
"**/*.py",
root_dir=sample_dir,
recursive=True,
include_hidden=True,
)
)
hidden_python_files = sorted(
set(all_python_files) - set(visible_python_files)
)
special_files = glob.glob(
glob.escape("[draft].txt"),
root_dir=sample_dir,
)
show_paths("Markdown 文件", markdown_files)
show_paths("可见 Python 文件", visible_python_files)
show_paths("额外发现的隐藏 Python 文件", hidden_python_files)
show_paths("特殊文件名", special_files)
if __name__ == "__main__":
main()运行结果:
Markdown 文件:
- README.md
- docs/intro.md
可见 Python 文件:
- app.py
- tests/test_app.py
额外发现的隐藏 Python 文件:
- .hidden/secret.py
特殊文件名:
- [draft].txt案例只查找并输出路径,不会读取、修改或删除文件。
批量操作安全边界
glob 本身是只读查找,但匹配结果经常会继续交给复制、移动或删除操作。批量修改之前应确认:
root_dir是否限制在预期目录。- 通配符是否匹配了额外文件或目录。
- 是否需要包含隐藏文件。
- 结果顺序是否会影响后续处理。
- 是否已经先输出并人工检查匹配列表。
不要把未经检查的宽泛规则直接交给 unlink()、move() 或 rmtree()。
小结
glob使用文件名通配符匹配路径,不使用正则表达式语法。glob()返回列表,iglob()返回可以逐个读取的迭代器。root_dir可以固定搜索范围,而不改变当前工作目录。**配合recursive=True可以递归进入子目录。glob默认忽略隐藏名称,可以通过include_hidden=True主动包含。glob.escape()可以按字面匹配包含特殊字符的文件名。Path.glob()和Path.rglob()返回Path对象,并且隐藏文件行为不同。
练习
- 使用
root_dir查找示例目录中的所有.txt文件。 - 使用
?匹配名称中只有一个可变字符的文件。 - 分别使用
glob()和iglob()递归查找 Python 文件。 - 比较默认模式和
include_hidden=True的结果。 - 使用
glob.escape()匹配[draft].txt。 - 使用
Path.rglob()重写综合案例中的 Markdown 文件查找。