Python glob.glob 的“跨平台坑”:Linux 严格区分大小写,Windows 却“睁一只眼闭一只眼”

大家好,我是你们的老朋友,今天来聊一个几乎每个做跨平台开发的同学都踩过的坑——Python 的 glob.glob() 在不同操作系统下的大小写敏感性差异。

你有没有遇到过这样的情况:

  • 在本地 Windows 开发机上,一切正常,脚本顺利找到所有 .txt 文件;
  • 扔到 Linux 服务器或 CI/CD 环境,突然报“文件找不到”;
  • 明明文件名就是 config.txt,你写的却是 Config.TXT,为什么 Windows 能跑通,Linux 就挂了?

答案就在文件系统和 glob 模块的设计上。

一、核心差异:文件系统说了算

Python 的 glob 模块模仿 Unix shell 的通配符匹配,但匹配是否区分大小写,实际上取决于底层文件系统,而不是 Python 本身刻意统一。

  • Linux / macOS(默认 APFS/HFS+ 区分大小写模式)
    文件系统是真正区分大小写的。file.txtFile.TXT 是两个完全不同的文件。
    所以 glob.glob("*.txt") 只匹配小写 .txt 结尾的文件,FILE.TXT 根本不会被选中。

  • Windows(NTFS 默认行为)
    文件系统是不区分大小写的(case-insensitive),但保留大小写(case-preserving)。
    file.txtFile.TXTFILE.txt 其实指向同一个文件。
    因此 glob.glob("*.txt") 会匹配所有这些变体——Windows 底层在匹配时忽略了大小写。

官方文档(Python 3.12+)虽然没有直接大段强调,但社区和 Stack Overflow 上无数案例证实:glob 的模式匹配会跟随文件系统的语义。这导致跨平台代码最常见的“silent bug”——在 Windows 上“意外”多匹配,在 Linux 上“严格”漏匹配。

二、真实案例:你可能正在经历的血泪史

场景1:数据处理脚本
你写了个批量重命名工具:

import glob
for f in glob.glob("data/*.JPG"):
    # 处理 JPG 文件

在 Windows 上,它能抓到 .jpg.JPG.JpG 所有文件,完美。
部署到 Linux 服务器,只处理了全大写的,剩下几千张小写后缀的图片被忽略……

场景2:配置文件加载
公司很多旧项目配置文件命名不规范,有人叫 settings.yaml,有人叫 Settings.YAML
本地 Windows 测试通过,上线 Docker(Linux)容器直接崩溃。

场景3:CI/CD 流水线
GitHub Actions / GitLab CI 默认 Linux runner,Windows runner 少见。
你本地调试 OK,提交 PR 却失败——原因往往就是 glob 没抓到文件。

三、如何写出真正跨平台的 glob 代码?(推荐方案)

目标:大多数项目希望统一行为,推荐默认按区分大小写处理(更安全、更接近 Linux 生产环境),必要时显式放宽。

方案1:最推荐 —— 统一转小写后过滤(简单、可靠)

import glob
import os

pattern = "data/*.txt"          # 你原本想匹配的模式
candidates = glob.glob(pattern, recursive=True)

# 无论 Windows/Linux,最终只保留真正以 .txt 结尾(忽略大小写)的文件
target_files = [
    f for f in candidates
    if os.path.splitext(f)[1].lower() == ".txt"
    # 或者更严格:f.lower().endswith(".txt")
]

print(target_files)

优点:代码简单,一行过滤解决所有问题。
缺点:如果目录里有海量文件,先 glob 再过滤会有轻微性能开销(但通常可忽略)。

方案2:用 pathlib(Python 3.5+ 现代写法)

from pathlib import Path

folder = Path("data")
# glob 还是会受系统影响
all_txt = list(folder.glob("*.txt"))          # Windows 多匹配,Linux 严格

# 推荐做法:直接用 iterdir + 后缀判断
target_files = [
    p for p in folder.iterdir()
    if p.is_file() and p.suffix.lower() == ".txt"
]

iterdir() 列出所有文件,再自己判断后缀,彻底绕过 glob 的系统差异。

方案3:想在 Windows 上强制“模拟区分大小写”

import glob
import os

def case_sensitive_glob(pattern):
    matches = glob.glob(pattern, recursive=True)
    if os.name != "nt":  # 非 Windows 直接返回
        return matches
    
    # Windows 上过滤:只保留 basename 完全匹配原模式(不忽略大小写)
    result = []
    for m in matches:
        base = os.path.basename(m)
        # 如果模式是 *.txt,这里需要更智能匹配(可结合 fnmatch)
        if base.lower().endswith(".txt"):  # 或者更精确匹配
            result.append(m)
    return result

更高级的可以用 fnmatch + 自定义过滤,但大多数场景方案1或2就够了。

四、一个小彩蛋:macOS 的“隐藏雷区”

macOS 默认文件系统(APFS)是区分大小写的,但很多用户会格式化成不区分大小写的卷(尤其是从旧 HFS+ 迁移)。
所以同一套代码,在不同 Mac 上 glob 行为可能都不一样!
建议:Mac 开发者也养成“后缀转小写判断”的习惯,避免阴间 bug。

五、写在最后:跨平台开发的正确姿势

  • 永远不要假设文件名大小写一致;
  • 优先用 pathlib + suffix.lower() 判断;
  • 在 README 或文档里注明:本项目假设文件名后缀不区分大小写,但代码已兼容 Linux 严格模式
  • CI 一定要用 Linux runner 测试(最严格的环境);
  • 如果项目真的需要完全忽略大小写匹配,可以用 glob.glob("**/*.[tT][xX][tT]", recursive=True) 这种土办法(丑但有效)。

你踩过这个坑吗?欢迎评论区分享你的“血泪经历”或更优雅的解决方案~

下期预告:Python 文件路径那些年我们踩过的“斜杠 vs 反斜杠”大坑。

点个在看,下次踩坑少一些~