C 语言模糊匹配的实现方法: glob() 与 fnmatch()的分析
在 Linux/Unix C 语言编程中,我们经常需要处理文件路径、通配符匹配、自动化构建脚本、工具链等问题。
glob() 和 fnmatch() 是两个非常常见但容易混淆的接口,它们都支持类似 shell 中的通配符匹配(如 *.c、src/*.h),但实际用途却完全不同。
一、两个函数的定位差异
虽然 glob() 和 fnmatch() 都支持 shell 风格的模式字符串,但它们的职责完全不一样:
| 项目 | glob() | fnmatch() |
|---|---|---|
| 核心功能 | 执行文件名模式扩展(filename expansion) | 执行字符串模式匹配(pattern matching) |
| 是否访问文件系统 | ✔️ 是,会遍历目录树 | ❌ 不会,只比较两个字符串 |
| 输出 | 实际存在的路径列表 | 匹配结果:0 或 FNM_NOMATCH |
| 使用目的 | 查找文件(类似 shell 的 ls *.c) | 判断字符串是否符合模式(类似 shell 的 case 匹配) |
| 性能开销 | 较高(IO + 目录遍历) | 较低(纯计算) |
一句话总结:
glob() 是“查文件”,fnmatch() 是“比字符串”。
二、glob():文件路径扩展的系统级工具
1. 功能概述
glob() 会根据通配符模式自动访问文件系统、遍历目录,并返回所有匹配的路径。它的行为类似 shell 的通配展开:
$ echo *.c
main.c util.c parser.c对应到 C 代码中就是:
glob("*.c", 0, NULL, &results);2. 使用示例
#include <glob.h>
#include <stdio.h>
int main() {
glob_t results;
glob("src/*.h", 0, NULL, &results);
for (size_t i = 0; i < results.gl_pathc; i++) {
printf("%s\n", results.gl_pathv[i]);
}
globfree(&results);
return 0;
}输出是文件系统中真实存在的文件。
3. 使用场景
- 批量处理文件(遍历
.c,.log, 等) - 日志扫描工具
- 构建系统(如 make 的模式规则)
- Shell 工具开发(实现类似
grep *.txt的功能)
4. 缺点
- IO 开销高
- 遍历目录可能耗时
- 不适合在性能敏感的循环中使用
三、fnmatch():轻量级字符串模式匹配
1. 功能概述
fnmatch() 完全不访问文件系统,它用来判断一个字符串是否满足模式规则。
示例:
fnmatch("*.c", "main.c", 0) == 0 // 匹配
fnmatch("src/*.h", "src/util.h", 0) == 02. 使用示例
#include <fnmatch.h>
#include <stdio.h>
int main() {
if (fnmatch("*.txt", "hello.txt", 0) == 0)
printf("match\n");
else
printf("no match\n");
return 0;
}3. 使用场景
- 在过滤逻辑中判断字符串是否匹配模式
- 遍历文件名时根据规则过滤(但不负责查找)
- 模式过滤系统(如 nginx、ftp 服务、文件同步工具)
- 实现用户自定义配置中的通配符匹配
4. 优势
- 极快(只做字符串运算)
- 无需访问文件系统
- 可嵌入各种复杂过滤管线
四、glob() 与 fnmatch() 的协同使用
在许多应用中,两者需要配合使用:
glob()获取所有潜在候选文件;fnmatch()根据更复杂的规则再次过滤。
例如构建系统:
glob("src/*.c", 0, NULL, &g);
for each file in g:
if fnmatch("test_*.c", filename):
skip();
else
compile();这种组合方式在大规模工程中非常常见。
五、底层原理:为什么需要两个函数?
glob() 的设计哲学
它属于 POSIX 标准的一部分,用于实现 shell 行为。其核心能力包括:
- 目录遍历
- 路径拼接
- 文件系统访问
- 错误处理(如权限、符号链接)
其逻辑接近:
模式切片 → 遍历目录 → 按段匹配 → 合并结果fnmatch() 的设计哲学
是一个纯模式匹配引擎,用于匹配:
*任意字符序列?任意单个字符[...]字符类- 可选的
FNM_PATHNAME、FNM_CASEFOLD等行为控制
它和正则相比更轻量、更接近 shell 的语法。
六、性能对比
| 方面 | glob() | fnmatch() |
|---|---|---|
| 时间复杂度 | O(N × M)(N=文件数) | O(M)(纯字符串匹配) |
| 是否做 IO | ✔️ | ❌ |
| 开销 | 高 | 极低 |
| 适合场景 | 预加载文件列表 | 高频匹配 |
如果你的匹配逻辑在循环中执行,绝不能用 glob()。
七、使用建议
| 场景需求 | 使用函数 | 说明 |
|---|---|---|
| 获取真实存在的文件 | glob() | 会访问文件系统并返回所有匹配的路径 |
自动完成路径解析(如 src/**/*.c) | glob() | 支持多层级目录展开 |
| 实现 shell 风格的命令工具 | glob() | 行为与 shell 通配符扩展一致 |
| 根据模式过滤字符串 | fnmatch() | 纯字符串匹配,不访问文件系统 |
| 实现自己的文件过滤逻辑 | fnmatch() | 常与 readdir/glob 搭配使用 |
| 高性能匹配 | fnmatch() | 仅做字符串运算,速度快 |
| 避免 IO 操作 | fnmatch() | 不进行目录遍历,无文件系统开销 |
八、总结
glob() 和 fnmatch() 看似相似,但定位完全不同:
- glob() = 文件查找工具(带文件系统访问)
- fnmatch() = 字符串匹配工具(纯计算,无 IO)
你可以将它们理解为:
glob() 类似 shell 的 “文件名展开”
fnmatch() 类似 shell 的 “字符串匹配”
THE END