C 语言模糊匹配的实现方法: glob() 与 fnmatch()的分析

在 Linux/Unix C 语言编程中,我们经常需要处理文件路径、通配符匹配、自动化构建脚本、工具链等问题。

glob()fnmatch() 是两个非常常见但容易混淆的接口,它们都支持类似 shell 中的通配符匹配(如 *.csrc/*.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) == 0

2. 使用示例

#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() 的协同使用

在许多应用中,两者需要配合使用:

  1. glob() 获取所有潜在候选文件;
  2. 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_PATHNAMEFNM_CASEFOLD 等行为控制

它和正则相比更轻量、更接近 shell 的语法。

六、性能对比

方面glob()fnmatch()
时间复杂度O(N × M)(N=文件数)O(M)(纯字符串匹配)
是否做 IO✔️
开销极低
适合场景预加载文件列表高频匹配

如果你的匹配逻辑在循环中执行,绝不能用 glob()

七、使用建议

场景需求使用函数说明
获取真实存在的文件glob()会访问文件系统并返回所有匹配的路径
自动完成路径解析(如 src/**/*.cglob()支持多层级目录展开
实现 shell 风格的命令工具glob()行为与 shell 通配符扩展一致
根据模式过滤字符串fnmatch()纯字符串匹配,不访问文件系统
实现自己的文件过滤逻辑fnmatch()常与 readdir/glob 搭配使用
高性能匹配fnmatch()仅做字符串运算,速度快
避免 IO 操作fnmatch()不进行目录遍历,无文件系统开销

八、总结

glob()fnmatch() 看似相似,但定位完全不同:

  • glob() = 文件查找工具(带文件系统访问)
  • fnmatch() = 字符串匹配工具(纯计算,无 IO)

你可以将它们理解为:

glob() 类似 shell 的 “文件名展开”
fnmatch() 类似 shell 的 “字符串匹配”

THE END