
1. 项目概述为什么我们需要 glob如果你写过 Python 脚本处理过文件大概率遇到过这样的场景你需要批量读取某个文件夹下所有以.txt结尾的日志文件或者找出所有以data_开头的 CSV 文件。新手可能会立刻想到os.listdir()然后写一个循环用str.endswith()或正则表达式去过滤。这当然能实现但代码会显得有点啰嗦而且当匹配规则稍微复杂一点比如“所有子目录下的.jpg文件”代码的复杂度就会直线上升。这时候glob库就该登场了。它不是一个需要额外安装的第三方库而是 Python 标准库的一员这意味着你可以在任何 Python 环境中直接使用它。glob的核心价值在于它提供了一种基于 Unix shell 风格的通配符规则来匹配文件路径名的方法。简单说你可以用像*.txt、data_*.csv、**/*.jpg这样直观的“模式字符串”一次性获取所有符合规则的文件路径列表。这极大地简化了文件查找和筛选的代码让脚本更清晰、更易维护。我最初接触glob是在处理一个图像预处理项目需要从几十个不同日期的子文件夹里收集特定格式的图片。手动写递归遍历加条件判断代码写了小半页还容易出错。换成glob后一行glob.glob(‘**/*.png’, recursiveTrue)就搞定了那种“柳暗花明”的感觉至今记忆犹新。它特别适合数据分析、自动化脚本、日志处理、素材整理等任何涉及批量文件操作的场景。无论你是刚入门的新手还是需要编写健壮工具的老手glob都是一个值得放进工具箱的利器。2. glob 的核心机制与模式语法解析要用好glob关键在于理解它的模式匹配规则。这套规则源自 Unix shell非常直观但也有一些细节需要注意。2.1 基础通配符*、?、[]这是glob模式的三驾马车构成了最基本的匹配能力。星号*匹配任意数量的任意字符包括零个字符。这是最常用的通配符。*.py匹配当前目录下所有以.py结尾的文件如main.py、utils.py但不包括子目录中的sub/main.py。data_*匹配所有以data_开头的文件或目录如data_2023.csv、data_backup。*单独一个星号匹配当前目录下所有非隐藏的文件和目录。注意它不匹配以点.开头的隐藏文件。问号?匹配任意单个字符。image?.jpg匹配像image1.jpg、imageA.jpg这样的文件但不匹配image10.jpg因为10是两个字符。log_2023-??-??.txt可以匹配log_2023-01-01.txt其中的??分别匹配月份和日期的两位数字。字符集[]匹配括号内列出的任意一个字符。report_[abc].txt只匹配report_a.txt、report_b.txt、report_c.txt。支持范围表示[0-9]匹配任意单个数字[a-z]匹配任意小写字母。image[0-9][0-9].jpg匹配image00.jpg到image99.jpg。注意字符集是“单字符”匹配。[abc]*表示以 a、b 或 c 开头后面跟任意字符的文件。注意在 Windows 系统上glob对路径分隔符的处理是透明的。你可以在模式中使用正斜杠/glob会在内部处理为当前系统的分隔符。但为了代码的跨平台清晰性建议使用os.path.join或pathlib.Path来构建路径。2.2 高级模式** 与 recursive 参数当你的文件散落在多层子目录中时基础通配符就力不从心了。这时需要用到双星号**。双星号**匹配零个或多个目录层级。这相当于递归进入所有子目录。单独使用**本身没有意义需要结合其他模式。**/*.py匹配当前目录及其所有子目录递归下的所有.py文件。docs/**/*.md匹配docs目录下所有层级的.md文件。这里有一个关键点**模式只有在调用glob.glob()或glob.iglob()时将参数recursive设置为True时才生效。如果recursiveFalse默认值**会被当作两个普通的星号*来处理。import glob # 假设目录结构为project/main.py, project/src/utils.py, project/docs/readme.md # recursiveFalse (默认) files glob.glob(‘**/*.py’) # 匹配不到任何文件因为 ** 未被递归解析 print(files) # 输出: [] (可能是空列表) # recursiveTrue files glob.glob(‘**/*.py’, recursiveTrue) print(files) # 输出: [‘main.py’, ‘src/utils.py’] (具体顺序可能不同)2.3 路径与模式的组合模式字符串可以包含目录路径。glob会基于你提供的路径进行匹配。绝对路径匹配glob.glob(‘/home/user/projects/*.py’)从绝对路径开始查找。相对路径匹配glob.glob(‘./data/*.csv’)或glob.glob(‘data/*.csv’)从当前工作目录开始查找。在特定目录下递归glob.glob(‘logs/**/*.log’, recursiveTrue)只在logs目录下进行递归搜索。一个常见的技巧是使用os.path.join来构建跨平台的路径模式import os, glob folder ‘my_data’ pattern ‘*.csv’ full_pattern os.path.join(folder, pattern) # 在 Windows 上生成 ‘my_data\\*.csv‘在 Linux/macOS 上生成 ’my_data/*.csv‘ csv_files glob.glob(full_pattern)3. glob 库的实战接口详解glob库非常精简主要就两个函数glob.glob()和glob.iglob()。理解它们的区别是高效使用的关键。3.1 glob.glob()返回列表简单直接glob.glob(pathname, *, recursiveFalse)是最常用的函数。它接受一个模式字符串扫描文件系统并将所有匹配的路径以**列表list**的形式返回。特点与适用场景一次性加载所有匹配结果会一次性加载到内存的列表中。结果可复用因为返回的是列表你可以方便地对其进行排序、切片、多次遍历等操作。简单场景首选当匹配的文件数量不是特别巨大比如几千个以内且你需要对结果集进行复杂操作时使用glob()非常方便。import glob # 查找当前目录所有 .txt 文件 txt_files glob.glob(‘*.txt’) print(f“找到 {len(txt_files)} 个文本文件”) for f in sorted(txt_files): # 可以对列表排序 print(f) # 查找特定目录及其子目录下所有 .jpg 和 .png 图片 image_patterns [‘images/**/*.jpg’, ‘images/**/*.png’] all_images [] for pattern in image_patterns: all_images.extend(glob.glob(pattern, recursiveTrue)) print(all_images)注意事项返回的路径形式取决于你输入的模式。如果模式是相对路径如*.py返回的也是相对路径如果是绝对路径返回的也是绝对路径。列表的顺序是任意的取决于操作系统和文件系统。如果需要确定的顺序如按文件名、修改时间务必对结果列表进行排序sorted()。3.2 glob.iglob()返回迭代器处理海量文件glob.iglob(pathname, *, recursiveFalse)是glob()的迭代器版本。它返回一个生成器迭代器generator iterator而不是列表。特点与适用场景惰性求值它不会一次性找出所有文件并存入内存而是在你遍历迭代器时逐个“生成”匹配的路径。内存友好这是它最大的优势。当你需要处理一个包含数万甚至数百万个文件的目录时使用iglob()可以避免内存被巨大的路径列表撑爆。即时处理适合“找到一个处理一个”的流水线式操作。import glob # 使用 iglob 处理大量日志文件 log_iterator glob.iglob(‘/var/log/**/*.log’, recursiveTrue) for log_path in log_iterator: # 每次循环迭代器才去获取下一个匹配的路径 try: with open(log_path, ‘r’, encoding‘utf-8’) as f: # 处理当前日志文件例如分析最后一行 lines f.readlines() if lines: print(f“{log_path}: {lines[-1][:50]}...”) except (IOError, UnicodeDecodeError) as e: print(f“无法读取文件 {log_path}: {e}”) # 在处理完一个文件后它的路径就可以从内存中释放了glob()vsiglob()如何选择这是一个典型的空间与时间权衡。99% 的情况下文件数量不多用glob()拿列表更方便。只有当你明确知道或怀疑目标目录下文件数量极多或者你的脚本需要作为后台服务长期运行、处理源源不断的新文件时才需要考虑使用iglob()来降低内存 footprint。3.3 隐藏文件与点号开头的文件默认情况下glob的*通配符不会匹配以点号.开头的文件在 Unix-like 系统中是隐藏文件。这是为了遵循 shell 的惯例。如果你需要匹配隐藏文件必须在模式中显式地包含点号。import glob # 不匹配 .gitignore, .env 等文件 normal_files glob.glob(‘*’) print(normal_files) # 输出: [‘main.py‘, ’data‘, ’README.md’] # 匹配所有文件包括隐藏文件 all_files glob.glob(‘.*’) glob.glob(‘[!.]*’) # 一种技巧匹配‘.‘开头和非’.’开头的 # 更直接的方式如果需要匹配当前目录所有包括隐藏文件可以结合 os.listdir import os all_files_os [f for f in os.listdir(‘.’) if not f.startswith(‘.’)] # 这样反而排除了隐藏文件 # 要包含隐藏文件只需去掉 if 判断 all_files_all os.listdir(‘.’)对于递归模式中的隐藏目录如.git**在recursiveTrue时默认会进入并匹配其中的文件但匹配结果不会包含目录本身除非模式以/结尾但glob主要用于匹配文件。4. 超越 glob与 os 和 pathlib 的协同作战glob擅长模式匹配但文件管理不止于查找。Python 的标准库中os和pathlib模块提供了更全面的文件和目录操作功能。将它们与glob结合能发挥更大威力。4.1 结合 os 模块进行文件操作找到文件后常见的后续操作包括获取文件信息、重命名、删除等。os.path子模块和os本身是这方面的主力。import glob import os import shutil # 场景找到所有临时文件 (.tmp) 并删除 tmp_files glob.glob(‘**/*.tmp’, recursiveTrue) for tmp_file in tmp_files: try: os.remove(tmp_file) # 删除文件 print(f“已删除: {tmp_file}”) except OSError as e: print(f“删除失败 {tmp_file}: {e}”) # 场景找到所有 .jpg 文件并复制到备份目录 backup_dir ‘backup_images’ os.makedirs(backup_dir, exist_okTrue) # 创建备份目录如果已存在则不报错 jpg_files glob.glob(‘photos/**/*.jpg’, recursiveTrue) for jpg in jpg_files: # 构造备份路径保持原有目录结构 relative_path os.path.relpath(jpg, start‘photos’) backup_path os.path.join(backup_dir, relative_path) # 确保目标目录存在 os.makedirs(os.path.dirname(backup_path), exist_okTrue) # 复制文件 shutil.copy2(jpg, backup_path) # copy2 会尝试保留元数据 print(f“已备份: {jpg} - {backup_path}”) # 场景获取文件大小和修改时间 for file in glob.glob(‘*.py’): size os.path.getsize(file) # 文件大小字节 mtime os.path.getmtime(file) # 最后修改时间时间戳 print(f“{file}: {size} bytes, 修改于 {time.ctime(mtime)}”)4.2 拥抱现代 pathlib 的面向对象路径Python 3.4 引入的pathlib模块提供了面向对象的文件系统路径操作方法。它的Path对象也有.glob()和.rglob()方法功能上与glob模块类似但集成在路径对象中写起来更流畅。from pathlib import Path # 使用 Path.glob() - 非递归相当于 glob.glob(recursiveFalse) current_dir Path(‘.’) py_files list(current_dir.glob(‘*.py’)) # 返回的是 Path 对象的生成器用 list() 转为列表 for py_file in py_files: print(py_file, py_file.stat().st_size) # Path 对象可以直接获取属性 # 使用 Path.rglob() - 递归相当于 glob.glob(recursiveTrue) all_py_files list(Path(‘.’).rglob(‘*.py’)) for py_file in all_py_files: print(py_file.relative_to(Path(‘.’))) # 输出相对路径 # pathlib 的便利性链式调用 # 找到所有大于 1MB 的 .log 文件 large_logs [p for p in Path(‘/var/log’).rglob(‘*.log’) if p.stat().st_size 1024*1024] # 结合 glob 模式进行复杂筛选 # 找到所有以 ‘test_’ 开头且不是 ‘.pyc’ 结尾的 Python 文件 test_files Path(‘src’).rglob(‘test_*.py’) valid_test_files [f for f in test_files if not f.name.endswith(‘.pyc’)]glob模块 vspathlib.Path.glob()如何选glob模块更轻量接口纯粹如果你只需要简单的模式匹配功能并且代码运行在较低版本的 Python3.4上它是唯一选择。pathlib更现代面向对象与路径的其他操作拼接、解析、属性获取结合得天衣无缝。如果你的项目基于 Python 3.4并且涉及复杂的路径操作强烈推荐使用pathlib。它的.rglob(‘*’)相当于glob(‘**/*’, recursiveTrue)写起来更简洁。5. 实战案例构建一个简易的日志文件清理工具理论说再多不如一个实际案例。假设我们是一个应用的服务端维护者应用会在logs目录下按日期生成子文件夹如2023-10-01里面存放着当天的日志文件app.log,error.log。我们需要一个工具定期清理超过 30 天的日志文件夹。我们将使用glob来识别需要清理的目录并结合os或shutil进行删除。5.1 设计思路与目录结构假设假设日志目录结构如下project/ ├── log_cleaner.py (我们的脚本) └── logs/ ├── 2023-09-01/ │ ├── app.log │ └── error.log ├── 2023-09-02/ │ ├── app.log │ └── error.log ├── ... └── 2023-10-01/ ├── app.log └── error.log我们的目标找出logs目录下所有格式为YYYY-MM-DD的文件夹判断其日期是否早于 30 天前如果是则删除整个文件夹。5.2 分步实现与代码详解#!/usr/bin/env python3 日志目录清理工具。 删除指定日志根目录下所有超过指定天数的、按日期命名的子目录。 import glob import os import shutil from datetime import datetime, timedelta import re def cleanup_old_logs(logs_root_dir, days_to_keep30): 清理旧的日志目录。 Args: logs_root_dir (str): 日志根目录的路径。 days_to_keep (int): 需要保留日志的天数。早于此天数的目录将被删除。 # 1. 安全检查确认目录存在 if not os.path.isdir(logs_root_dir): print(f“错误目录 ‘{logs_root_dir}’ 不存在。”) return # 2. 计算截止日期 cutoff_date datetime.now() - timedelta(daysdays_to_keep) print(f“当前时间{datetime.now().strftime(‘%Y-%m-%d’)}”) print(f“将删除早于 {cutoff_date.strftime(‘%Y-%m-%d’)} 的日志目录。”) # 3. 使用 glob 查找所有符合日期格式的子目录 # 模式 ‘[0-9]’ 确保匹配数字。这里我们匹配 4位数字-2位数字-2位数字 的目录名 # 注意glob 模式本身没有严格的日期有效性校验它只匹配格式。 date_dir_pattern os.path.join(logs_root_dir, ‘[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]’) # 更精确一点可以用’[0-9][0-9][0-9][0-9]-[0-1][0-9]-[0-3][0-9]‘但依然无法校验2月30日这种。 potential_dirs glob.glob(date_dir_pattern) deleted_count 0 error_count 0 # 4. 遍历并判断每个目录 for dir_path in potential_dirs: dir_name os.path.basename(dir_path) # 4.1 使用正则表达式进一步验证并解析日期 # 这是对 glob 匹配结果的二次校验更安全。 date_match re.match(r’^(\d{4})-(\d{2})-(\d{2})$’, dir_name) if not date_match: # 如果目录名不符合严格的 YYYY-MM-DD跳过虽然 glob 已过滤但双重检查更稳 continue try: year, month, day map(int, date_match.groups()) dir_date datetime(year, month, day) except ValueError: # 处理无效日期如 2023-02-30 print(f“跳过无效日期目录{dir_name}”) continue # 4.2 判断目录日期是否早于截止日期 if dir_date cutoff_date: print(f“标记待删除 [{dir_date.strftime(‘%Y-%m-%d’)}]: {dir_path}”) try: # 4.3 执行删除操作 # 使用 shutil.rmtree 删除整个目录树 shutil.rmtree(dir_path) print(f“ 成功删除。”) deleted_count 1 except Exception as e: print(f“ 删除失败错误: {e}”) error_count 1 else: # 可选打印保留的目录 # print(f“保留 [{dir_date.strftime(‘%Y-%m-%d’)}]: {dir_path}”) pass # 5. 输出总结报告 print(“\n” “”*50) print(f“清理完成。”) print(f“扫描到目录数{len(potential_dirs)}”) print(f“成功删除{deleted_count}”) print(f“删除失败{error_count}”) if __name__ ‘__main__’: # 使用示例清理 ‘./logs’ 目录下超过 30 天的日志 LOG_DIR ‘./logs’ # 修改为你的日志目录 DAYS_TO_KEEP 30 cleanup_old_logs(LOG_DIR, DAYS_TO_KEEP)5.3 关键点解析与避坑指南模式设计‘[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]’这个模式利用了字符集[0-9]来匹配数字。它比*更精确能过滤掉非日期格式的目录。但请注意它无法区分2023-13-01无效月份或2023-02-30无效日期所以后续的日期解析和try-except至关重要。二次校验glob匹配后我们立即用正则表达式re.match(r’^(\d{4})-(\d{2})-(\d{2})$’, dir_name)进行二次校验和日期解析。这是一个非常好的实践可以防止因目录名意外符合模式但内容非法导致的程序崩溃。安全删除shutil.rmtree(dir_path)会递归删除目录及其所有内容不可逆。在生产环境中运行此类脚本前务必先进行“模拟运行”dry run。可以在删除前先打印出待删除的目录确认无误后再真正执行。一个技巧是给函数加一个dry_runTrue的参数。def cleanup_old_logs(logs_root_dir, days_to_keep30, dry_runTrue): # ... 前面的逻辑相同 ... if dir_date cutoff_date: if dry_run: print(f“[模拟] 将删除 {dir_path}”) else: try: shutil.rmtree(dir_path) # ... 记录成功 ... except Exception as e: # ... 记录失败 ...路径处理使用os.path.join(logs_root_dir, pattern)来构建完整的模式路径保证了跨平台兼容性。错误处理删除操作 (shutil.rmtree) 被包裹在try-except中。可能失败的原因包括文件正在被其他进程使用、权限不足、路径是符号链接指向不存在的目标等。记录错误而不是让整个脚本崩溃是健壮性编程的体现。这个案例展示了glob如何作为文件系统操作的“侦察兵”快速定位目标然后由其他模块os,shutil,datetime,re来完成具体的业务逻辑日期计算、验证、删除。这种分工协作让代码清晰又强大。6. 性能考量、边界情况与最佳实践在简单场景下glob的性能不是问题。但当目录树非常庞大例如数十万个文件或者模式非常宽泛如**/*时就需要一些考量。6.1 性能优化浅谈避免过于宽泛的模式**/*会遍历所有文件和目录代价高昂。尽量使用更精确的模式缩小搜索范围例如**/*.data比**/*好project/src/**/*.py比**/*.py好。理解文件系统缓存操作系统会缓存目录列表。连续运行相同的glob查询第二次通常会快很多。但对于首次遍历巨型目录仍需耐心。iglob是内存友好的选择如前所述面对海量文件使用glob.iglob()生成器可以避免内存峰值。考虑替代方案对于超大规模、性能要求极高的文件遍历可能需要考虑系统级的工具如find命令或特定平台的高效 API如 Windows 的FindFirstFile/FindNextFile并通过subprocess或ctypes调用。但在绝大多数 Python 应用场景中glob已经足够高效。6.2 常见边界情况处理符号链接Symbolic Linksglob默认会跟随符号链接。如果符号链接指向一个目录**在recursiveTrue时会进入该目录。这可能导致循环链接自己链自己引发的递归深度问题。如果你的目录结构中存在不可控的符号链接需要格外小心。pathlib的.glob()方法默认也跟随链接。权限问题当glob尝试进入一个没有读取权限的子目录时行为取决于操作系统。通常它会默默地跳过该目录不会报错也不会匹配其中的文件。这可能导致结果不完整但无错误提示。如果你的脚本对完整性要求高可能需要额外的权限检查或错误捕获机制但glob本身不提供这个接口。并发修改如果在glob扫描文件系统的瞬间正好有文件被创建或删除结果可能会不一致可能包含刚删除的文件或漏掉刚创建的文件。对于大多数场景这不是问题但对于需要强一致性的关键任务需要考虑加锁或使用事务性文件系统操作。模式中的特殊字符如果你的文件名本身包含*、?、[、]这些glob的特殊字符你需要对它们进行转义才能进行字面匹配。glob模块没有内置转义函数但你可以用fnmatch.translate()来辅助或者更简单地在这些字符前加上[]。例如要匹配名为file[1].txt的文件模式应写为file[[]1].txt。6.3 个人总结的最佳实践根据多年的使用经验我总结了以下几点明确当前工作目录使用相对路径模式时心里要清楚当前工作目录 (os.getcwd()) 是什么。在复杂的项目或脚本中最好使用绝对路径或者在使用glob前先用os.chdir()切换到目标目录。先测试后操作在编写删除、移动等破坏性操作的脚本时永远先做一个只打印不执行的 dry run。用glob列出目标文件确认无误后再替换成真正的操作代码。组合使用 pathlib在新项目Python 3.4中优先考虑使用pathlib.Path。它的.glob()和.rglob()方法更符合面向对象的思维并且与路径的其他操作无缝衔接。pathlib是未来。注释你的模式复杂的glob模式可能像正则表达式一样难以理解。在代码旁边添加简短注释说明这个模式意图匹配什么例如# 匹配所有后跟日期的数据文件。处理“无结果”glob.glob()在找不到匹配项时返回空列表[]。你的代码应该能优雅地处理这种情况而不是假设一定有结果。例如matching_files glob.glob(some_pattern) if not matching_files: print(f“警告未找到匹配模式 ‘{some_pattern}’ 的文件。”) # 或者进行其他逻辑处理 return for file in matching_files: process(file)glob库就是这样一个小而美的工具。它没有试图解决所有文件系统问题而是专注于“模式匹配”这一个点并将其做到极致。掌握它能让你在 Python 中处理文件时如虎添翼写出更简洁、更优雅的代码。下次当你需要写循环和endswith来过滤文件时不妨停下来想想能不能用一行glob搞定