Files
BakNRet/BakNRet/Public/Get-BaknretRegexExclude.ps1
T
Shuery 42f02d0eca refactor: 公共面补 BakNRet 前缀,产品名大小写全仓统一
16 个没有前缀的公共函数补上 BakNRet(Write-Log → Write-BakNRetLog、Resolve-BackupEntry →
Resolve-BakNRetBackupEntry、Find-ChildDirectoryByName → Find-BakNRetChildDirectoryByName 等),
另外把全仓的 Baknret 统一成 BakNRet(47 个文件、940 处、65 个定义文件重命名)。

这不是审美问题:静态分析直接拓出一条实据 —— Write-Log 与本机某个已装模块导出的命令
**重名**(PSAvoidOverwritingBuiltInCmdlets),而重名的后果是导入两个模块时有一方的命令被
静默遮蔽。补前缀正是这条规则的解法,改名后它归零。

为什么敢做这个规模:PowerShell 的函数名解析大小写不敏感,所以 Baknret → BakNRet 在功能
上是零风险;真正要验证的是 16 个补前缀的调用点,而 276 个断言几乎覆盖了每个函数。另外
"名字与文件名一致"这条不变式有断言盯着(加载器点源的文件集合 vs 磁盘)。

踩到并记下的坑:Windows 文件系统大小写不敏感,所以**只改大小写**的重命名会被 Move-Item
当成同一个文件而静默跳过 —— 同一批里同时改了名字的那 16 个文件却成功了,于是"看起来能跑"。
最后用"先移到临时名、再移到目标名"的两步走解决,判断与替换全部改用显式大小写敏感的形式
(-creplace / -cmatch)。

顺带把名录指纹缓存从 MD5 换成 SHA256(PSAvoidUsingBrokenHashAlgorithms):它只是缓存键,
没有兼容负担。

验收:test.ps1 9/9 全绿(7 与 5.1)、100 个文件两版解析零错、276 个断言全过、
构建工具仍能合回单文件(3300 行)。
2026-09-27 09:56:36 +08:00

75 lines
3.2 KiB
PowerShell
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
function Get-BakNRetRegexExclude {
<#
.SYNOPSIS
把一条 `!re:<正则>` 展开成若干 `-x!<归档内路径>` 参数。
.DESCRIPTION
7z 本身只认通配符,不认正则,所以正则只能由脚本自己遍历源目录后翻译成
一条条精确的 `-x!<完整归档内路径>`:
* 逐层遍历,命中"目录名或相对路径"就把该目录整个排除,并且**不再往下走**
(否则一个命中会产生成千上万条参数);
* 展开结果有上限(MaxMatches),超过就明确报错,而不是悄悄漏排除或写出超长命令行。
注意:`!<通配>`(例如 `!*Cache`)不走这里——它在 .NET 里是非法正则
(`*` 前没有可重复的表达式),仍然按"任意层级匹配组件名"翻译成 `-xr!`。
#>
param(
[Parameter(Mandatory = $true)]$Item,
[Parameter(Mandatory = $true)][string]$Pattern,
[int]$MaxMatches = 300
)
$arguments = @()
$errorText = $null
try {
$regex = [System.Text.RegularExpressions.Regex]::new(
$Pattern, [System.Text.RegularExpressions.RegexOptions]::IgnoreCase)
}
catch {
return [pscustomobject]@{ Arguments = @(); Matches = 0; Error = "排除正则非法:$Pattern —— $($_.Exception.Message)" }
}
$real = [string]$Item.RealPath
if (-not $real -or -not (Test-Path -LiteralPath $real)) {
return [pscustomobject]@{ Arguments = @(); Matches = 0; Error = $null }
}
$root = Get-Item -LiteralPath $real -Force -ErrorAction SilentlyContinue
if (-not $root) { return [pscustomobject]@{ Arguments = @(); Matches = 0; Error = $null } }
if (-not $root.PSIsContainer) {
if ($regex.IsMatch($root.Name)) { $arguments += "-x!$($Item.ArchivePath)" }
return [pscustomobject]@{ Arguments = @($arguments); Matches = $arguments.Count; Error = $null }
}
# 用显式栈做深度优先遍历:命中就整棵剪掉,所以匹配数是"命中的最浅层数"。
$stack = New-Object System.Collections.Generic.Stack[object]
foreach ($child in @(Get-ChildItem -LiteralPath $root.FullName -Force -ErrorAction SilentlyContinue)) {
$stack.Push(@{ Relative = $child.Name; Item = $child })
}
while ($stack.Count -gt 0) {
$node = $stack.Pop()
$relative = [string]$node.Relative
$entry = $node.Item
if ($regex.IsMatch($entry.Name) -or $regex.IsMatch($relative)) {
$arguments += ('-x!{0}\{1}' -f $Item.ArchivePath, ($relative -replace '/', '\'))
if ($arguments.Count -gt $MaxMatches) {
$errorText = "排除正则 $Pattern 命中的路径超过 $MaxMatches 条,7z 命令行会过长;请改用更粗的通配模式(例如 !*Cache)"
break
}
continue
}
if ($entry.PSIsContainer) {
foreach ($child in @(Get-ChildItem -LiteralPath $entry.FullName -Force -ErrorAction SilentlyContinue)) {
$stack.Push(@{ Relative = ('{0}\{1}' -f $relative, $child.Name); Item = $child })
}
}
}
return [pscustomobject]@{ Arguments = @($arguments); Matches = $arguments.Count; Error = $errorText }
}