是否存在从正则表达式提取类通配符过滤模式的已知方法?
背景
执行过滤操作时,将过滤逻辑尽可能推到上游(比如文件系统层面)能大幅提升性能。例如在PowerShell中,Get-ChildItem 'c:\temp\' -Filter '*.txt' 比 Get-ChildItem 'c:\temp\' | Where-Object {$_.Name -like '*.txt'} 效率高得多——因为前者是让文件系统直接过滤,后者是先拉取所有文件再在内存中筛选。
但有些场景上游不支持复杂过滤:比如要查找所有图像文件(*.png, *.gif, *.jpg, *.jpeg),要么多次调用Get-ChildItem分别指定不同过滤器(会重复遍历目录,还可能出现重复文件),要么只能全量拉取后下游过滤。此时可以先用一个宽松但有效的通配符掩码(比如*.*g*)在上游过滤掉大部分非目标文件,再用正则在下游精确筛选。
问题
是否存在成熟方法,能从正则表达式中提取出一个类通配符掩码(即简单的*/?风格模式),这个掩码能作为上游过滤的条件,尽可能排除无关内容,同时保证所有匹配正则的条目都能通过该掩码?
举个例子:
- 正则
\.(?:png|gif|jpg|jpeg)$→ 掩码*.*g* - 正则
\.(?:jpg|jpeg)$→ 掩码*.jp*g - 正则
\.(?:png|gif|jpg|jpeg|webp)$→ 掩码*.*
解决方案
这类转换没有通用的标准算法,但针对常见场景(比如文件后缀匹配、固定枚举类正则)可以设计针对性的实现,核心思路是找到所有匹配正则的字符串必然包含的公共特征,用*替换可变部分,生成一个尽可能严格的超集掩码(掩码匹配范围 ≥ 正则匹配范围)。
核心实现逻辑
- 提取正则中的固定公共特征
- 对于枚举式的正则分支(比如
(png|gif|jpg|jpeg)),提取所有分支的公共子串/字符:比如png/gif/jpg/jpeg都包含字符g,这就是公共特征;而jpg/jpeg的公共前缀是jp、公共后缀是g。 - 对于带固定前后缀的正则,直接提取固定前缀和后缀,中间用
*填充。
- 对于枚举式的正则分支(比如
- 生成超集掩码
- 将提取到的公共特征用
*包裹/连接,确保所有匹配正则的字符串都能匹配该掩码。如果找不到任何公共特征,就退化为最宽泛的*或*.*。
- 将提取到的公共特征用
示例PowerShell实现
针对文件后缀匹配的场景,实现一个简单的ConvertTo-SimpleMask函数:
function ConvertTo-SimpleMask { param( [Parameter(Mandatory)] [string]$RegexPattern ) # 处理后缀匹配类正则(格式如 \.(?:png|gif|jpg)$) if ($RegexPattern -match '^\.\(?:([^)]+)\)$') { $extensions = $matches[1] -split '\|' # 第一步:找所有扩展名都包含的公共字符 $commonChars = @() $uniqueChars = $extensions | ForEach-Object { $_ -split '' | Where-Object { $_ } } | Select-Object -Unique foreach ($char in $uniqueChars) { if ($extensions | Where-Object { $_ -notcontains $char }) { continue } $commonChars += $char } # 第二步:尝试找最长公共前缀和后缀 $longestPrefix = $extensions[0] foreach ($ext in $extensions) { while ($ext -notlike "$longestPrefix*") { $longestPrefix = $longestPrefix.Substring(0, $longestPrefix.Length - 1) if ([string]::IsNullOrEmpty($longestPrefix)) { break } } } $longestSuffix = $extensions[0] foreach ($ext in $extensions) { while ($ext -notlike "*$longestSuffix") { $longestSuffix = $longestSuffix.Substring(1) if ([string]::IsNullOrEmpty($longestSuffix)) { break } } } # 生成掩码:优先用最长公共前后缀,没有则用公共字符,最后退化为*.* if (-not [string]::IsNullOrEmpty($longestPrefix) -and -not [string]::IsNullOrEmpty($longestSuffix)) { return "*.$longestPrefix*$longestSuffix" } elseif ($commonChars.Count -gt 0) { return "*.*$($commonChars[0])*" } else { return "*.*" } } # 其他正则场景可扩展逻辑,默认返回最宽泛的* return "*" }
局限性
这种方法只适用于结构简单的正则(枚举分支、固定前后缀)。对于包含任意字符匹配(.*)、范围匹配([a-z])、反向引用等复杂逻辑的正则,无法提取有效的严格掩码,只能退化为*,依赖下游的正则精确过滤。
内容的提问来源于stack exchange,提问作者JohnLBevan

