如何实现支持通配符和正则表达式的文件高级过滤搜索功能?
- 内容介绍
- 文章标签
- 相关推荐
本文共计1161个文字,预计阅读时间需要5分钟。
使用C++20的``库,不依赖任何内置通配符或正则表达式,手动实现高级搜索功能,以下是一个简单的示例代码:
常见错误是试图用 std::regex 直接匹配完整路径字符串(如 "C:/logs/*.log"),但这是错的:正则引擎不理解通配符语义,* 和 ? 在正则里有特殊含义,而用户输入的 "*.tmp" 是 shell 风格通配符,不是正则表达式。
- 先提取文件名部分(
p.filename().string()),不要对整个绝对路径做正则 - 若用户输入的是通配符(如
"data_??.csv"),需转换为等价正则再编译(std::regex_replace转义 + 替换) - 若用户明确要求正则(如
"^backup_\d{8}\.tar\.gz$"),跳过转换,直接构造std::regex - 注意 Windows 路径分隔符反斜杠在 C++ 字符串中需双写:
"C:\\temp\\*.log",否则编译报错或匹配失败
fnmatch 在跨平台项目里要慎用
POSIX 的 fnmatch(<fnmatch.h>)能原生支持 */?/[a-z],但它不是标准 C++ 接口,在 Windows 上不可用(除非用 MinGW 或第三方移植版)。MSVC 完全不带 fnmatch,强行链接会报 LNK2019。
如果你坚持用 fnmatch,必须加条件编译:
立即学习“C++免费学习笔记(深入)”;
#ifdef _WIN32 // 手动实现简易通配符匹配(见下节) #else #include <fnmatch.h> if (fnmatch(pattern.c_str(), name.c_str(), 0) == 0) { ... } #endif
-
fnmatch默认区分大小写,Windows 文件系统通常不区分,需传FNM_CASEFOLD标志(Linux 下不一定支持) - 某些嵌入式或精简环境(如 Android NDK r21+)也已移除
fnmatch,不能默认存在 - 它不支持正则,只解决通配符;想混用正则和通配符?还是得自己 dispatch
手写通配符转正则函数比想象中简单
把 "*.log" 转成 ^.*\.log$、把 "data_??.bin" 转成 ^data_.{2}\.bin$,只需几行替换逻辑,且完全可控、无依赖、跨平台。
关键点:转义正则元字符,再映射通配符:
std::string glob_to_regex(const std::string& glob) { std::string re = "^"; for (char c : glob) { switch (c) { case '*': re += ".*"; break; case '?': re += "."; break; case '.': re += "\."; break; case '\': re += "\\"; break; default: re += c; } } re += "$"; return re; }
- 必须以
^和$包裹,否则"log"会错误匹配"mylog.txt" -
.和在正则中有意义,必须转义;但用户输入的"config.file"中的点应被当作字面量 - 不处理
[abc]这类字符组——如果需要,就升级为完整 glob 解析器(如使用globcpp第三方库),但绝大多数场景只需*/?
性能敏感时避免重复编译 std::regex
每次遍历一个文件都调用 std::regex(pattern) 构造函数,开销不小——尤其 pattern 不变、文件成千上万时。正则编译是 CPU 密集操作,std::regex 在 MSVC 和 libstdc++ 上都不做内部缓存。
- 把
std::regex对象声明为static const或提前构造好,传入搜索函数 - 若 pattern 来自用户输入且可能变化,用
std::unordered_map<std::string, std::regex>缓存已编译正则(注意线程安全) - 更轻量替代:对纯前缀/后缀匹配(如
"*.so"),直接用name.extension() == ".so",比正则快 10 倍以上 - Windows 上
std::regex实现较慢,若只用简单通配符,手写字符串匹配(std::string_view+ 双指针)反而更稳更快
真正难的不是语法转换,而是统一接口设计:怎么让用户传一个字符串,自动识别它是 glob 还是 regex?加前缀标记(re:... / glob:...)最直白,也最不容易歧义。
本文共计1161个文字,预计阅读时间需要5分钟。
使用C++20的``库,不依赖任何内置通配符或正则表达式,手动实现高级搜索功能,以下是一个简单的示例代码:
常见错误是试图用 std::regex 直接匹配完整路径字符串(如 "C:/logs/*.log"),但这是错的:正则引擎不理解通配符语义,* 和 ? 在正则里有特殊含义,而用户输入的 "*.tmp" 是 shell 风格通配符,不是正则表达式。
- 先提取文件名部分(
p.filename().string()),不要对整个绝对路径做正则 - 若用户输入的是通配符(如
"data_??.csv"),需转换为等价正则再编译(std::regex_replace转义 + 替换) - 若用户明确要求正则(如
"^backup_\d{8}\.tar\.gz$"),跳过转换,直接构造std::regex - 注意 Windows 路径分隔符反斜杠在 C++ 字符串中需双写:
"C:\\temp\\*.log",否则编译报错或匹配失败
fnmatch 在跨平台项目里要慎用
POSIX 的 fnmatch(<fnmatch.h>)能原生支持 */?/[a-z],但它不是标准 C++ 接口,在 Windows 上不可用(除非用 MinGW 或第三方移植版)。MSVC 完全不带 fnmatch,强行链接会报 LNK2019。
如果你坚持用 fnmatch,必须加条件编译:
立即学习“C++免费学习笔记(深入)”;
#ifdef _WIN32 // 手动实现简易通配符匹配(见下节) #else #include <fnmatch.h> if (fnmatch(pattern.c_str(), name.c_str(), 0) == 0) { ... } #endif
-
fnmatch默认区分大小写,Windows 文件系统通常不区分,需传FNM_CASEFOLD标志(Linux 下不一定支持) - 某些嵌入式或精简环境(如 Android NDK r21+)也已移除
fnmatch,不能默认存在 - 它不支持正则,只解决通配符;想混用正则和通配符?还是得自己 dispatch
手写通配符转正则函数比想象中简单
把 "*.log" 转成 ^.*\.log$、把 "data_??.bin" 转成 ^data_.{2}\.bin$,只需几行替换逻辑,且完全可控、无依赖、跨平台。
关键点:转义正则元字符,再映射通配符:
std::string glob_to_regex(const std::string& glob) { std::string re = "^"; for (char c : glob) { switch (c) { case '*': re += ".*"; break; case '?': re += "."; break; case '.': re += "\."; break; case '\': re += "\\"; break; default: re += c; } } re += "$"; return re; }
- 必须以
^和$包裹,否则"log"会错误匹配"mylog.txt" -
.和在正则中有意义,必须转义;但用户输入的"config.file"中的点应被当作字面量 - 不处理
[abc]这类字符组——如果需要,就升级为完整 glob 解析器(如使用globcpp第三方库),但绝大多数场景只需*/?
性能敏感时避免重复编译 std::regex
每次遍历一个文件都调用 std::regex(pattern) 构造函数,开销不小——尤其 pattern 不变、文件成千上万时。正则编译是 CPU 密集操作,std::regex 在 MSVC 和 libstdc++ 上都不做内部缓存。
- 把
std::regex对象声明为static const或提前构造好,传入搜索函数 - 若 pattern 来自用户输入且可能变化,用
std::unordered_map<std::string, std::regex>缓存已编译正则(注意线程安全) - 更轻量替代:对纯前缀/后缀匹配(如
"*.so"),直接用name.extension() == ".so",比正则快 10 倍以上 - Windows 上
std::regex实现较慢,若只用简单通配符,手写字符串匹配(std::string_view+ 双指针)反而更稳更快
真正难的不是语法转换,而是统一接口设计:怎么让用户传一个字符串,自动识别它是 glob 还是 regex?加前缀标记(re:... / glob:...)最直白,也最不容易歧义。

