假设我有一个如下所示的正则表达式,但我将它从文件加载到变量 $regex 中,因此在设计时不知道它的内容是什么,但在运行时我可以发现它包含“version1”, “version2”、“version3”和“version4”命名组:
"Version (?<version1>\d),(?<version2>\d),(?<version3>\d),(?<version4>\d)"
...我有这些变量:
$version1 = "3"
$version2 = "2"
$version3 = "1"
$version4 = "0"
...我在文件中遇到以下字符串:
Version 7,7,0,0
...存储在变量 $input 中,因此 ($input -match $regex) 的计算结果为 $true。
如果我不知道它们在 $regex 中出现的顺序(我只知道 $正则表达式包括这些命名组)?
我找不到任何描述通过使用组名作为匹配索引将命名组替换为变量值的语法的参考 - 这甚至受支持吗?
编辑: 澄清 - 目标是替换任何类型的文本文件中的模板化版本字符串,其中给定文件中的版本字符串需要替换可变数量的版本字段(可能是 2、3 或所有 4 个字段)。例如,文件中的文本可能看起来像以下任何一种(但不限于这些):
#define SOME_MACRO(4, 1, 0, 0)
Version "1.2.3.4"
SomeStruct vs = { 99,99,99,99 }
用户可以指定一个文件集和一个正则表达式来匹配包含字段的行,最初的想法是各个字段将被命名组捕获。该实用程序具有应在文件中替换的各个版本字段值,但必须保留将包含替换的行的原始格式,并且仅替换请求的字段。
EDIT-2: 我认为我可以通过基于每个匹配项的位置和范围的子字符串计算得到我需要的结果,但希望 Powershell 的替换操作能够为我节省一些工作。
EDIT-3: 因此,正如 Ansgar 在下面正确而简洁地描述的那样,没有办法(仅使用原始输入字符串、您只知道命名组的正则表达式以及结果匹配项)使用“- replace" 操作(或其他正则表达式操作)来执行命名组的捕获的替换,同时保持原始字符串的其余部分不变。对于这个问题,如果有人好奇,我最终使用了下面的解决方案。YMMV,其他可能的解决方案。非常感谢 Ansgar 提供的反馈和选项。
在以下代码块中:
- $input 是要执行替换的文本行
- $regex 是从已验证包含至少一个受支持的命名组的文件中读取的正则表达式([string] 类型)
- $regexToGroupName 是一个哈希表,它将一个正则表达式字符串映射到一个组名数组,该数组按照 [regex]::GetGroupNames() 返回的数组的顺序排列,与它们出现的从左到右的顺序相匹配表达方式
- $groupNameToVersionNumber 是一个将组名映射到版本号的哈希表。
$regex 中命名组的约束只是(我认为)命名组中的表达式不能嵌套,并且应该在输入字符串中最多匹配一次。
# This will give us the index and extent of each substring
# that we will be replacing (the parts that we will not keep)
$matchResults = ([regex]$regex).match($input)
# This will hold substrings from $input that were not captured
# by any of the supported named groups, as well as the replacement
# version strings, properly ordered, but will omit substrings captured
# by the named groups
$lineParts = @()
$startingIndex = 0
foreach ($groupName in $regexToGroupName.$regex)
{
# Excise the substring leading up to the match for this group...
$lineParts = $lineParts + $input.Substring($startingIndex, $matchResults.groups[$groupName].Index - $startingIndex)
# Instead of the matched substring, we'll use the substitution
$lineParts = $lineParts + $groupNameToVersionNumber.$groupName
# Set the starting index of the next substring that we will keep...
$startingIndex = $matchResults.groups[$groupName].Index + $matchResults.groups[$groupName].Length
}
# Keep the end of the original string (if there's anything left)
$lineParts = $lineParts + $input.Substring($startingIndex, $input.Length - $startingIndex)
$newLine = ""
foreach ($part in $lineParts)
{
$newLine = $newLine + $part
}
$input= $newLine