假设我有一个如下所示的正则表达式,但是我将它从一个文件加载到变量$ 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中的顺序,我怎么能用字符串$ input中的$ regex替换$ version1,$ version2,$ version3,$ version4的值中的命名组(仅限I知道$ regex包含这些命名的组)?
我找不到任何引用,用于通过使用组名作为匹配的索引来使用变量值替换命名组的语法 - 这是否支持?
修改 为了澄清 - 目标是在任何类型的文本文件中替换模板化版本字符串,其中给定文件中的版本字符串需要替换可变数量的版本字段(可以是2,3或全部4个字段)。例如,文件中的文本可能看起来像这些(但不限于这些):
#define SOME_MACRO(4, 1, 0, 0)
Version "1.2.3.4"
SomeStruct vs = { 99,99,99,99 }
用户可以指定文件集和正则表达式以匹配包含字段的行,最初的想法是命名组将捕获各个字段。该实用程序具有应在文件中替换的单个版本字段值,但必须保留将包含替换的行的原始格式,并仅替换所请求的字段。
修改-2: 我想我可以根据每个匹配的位置和范围得到我需要的子串计算结果,但希望Powershell的替换操作能够为我节省一些工作。
修改-3: 因此,正如Ansgar在下面正确而简洁地描述的那样,没有办法(仅使用原始输入字符串,正则表达式,您只知道命名组,以及产生的匹配)来使用“-replace”操作(或其他正则表达式操作)执行命名组的捕获的替换,同时保留原始字符串的其余部分。对于这个问题,如果有人好奇,我最终使用下面的解决方案。 YMMV,其他解决方案可行。非常感谢Ansgar提供的反馈和选择。
在以下代码块中:
$ 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
答案 0 :(得分:4)
正则表达式不起作用,所以你不能。不是直接的,也就是说。您可以做什么(使用一个更合适的正则表达式,将您要保持的部分分组)是提取版本字符串,然后在第二步中用新版本字符串替换该子字符串:
$oldver = $input -replace $regexp, '$1,$2,$3,$4'
$newver = $input -replace $oldver, "$Version1,$Version2,$Version3,$Version4"
修改强>
如果你甚至不知道结构,你也必须从正则表达式中提取它。
$version = @($version1, $version2, $version3, $version4)
$input -match $regexp
$oldver = $regexp
$newver = $regexp
for ($i = 1; $i -le 4; $i++) {
$oldver = $oldver -replace "\(\?<version$i>\\d\)", $matches["version$i"]
$newver = $newver -replace "\(\?<version$i>\\d\)", $version[$i-1]
}
$input -replace $oldver, $newver
答案 1 :(得分:2)
简单解决方案
在您只想替换$input
文本中某处找到的版本号的情况下,您可以直接执行此操作:
$input -replace '(Version\s+)\d+,\d+,\d+,\d+',"`$1$Version1,$Version2,$Version3,$Version4"
在PowerShell中使用命名捕获
关于有关命名捕获的问题,可以使用大括号来完成。即。
'dogcatcher' -replace '(?<pet>dog|cat)','I have a pet ${pet}. '
给出:
I have a pet dog. I have a pet cat. cher
多次捕获问题&amp;溶液强>
您无法在同一替换语句中替换多个值,因为替换字符串用于所有内容。即如果你这样做了:
'dogcatcher' -replace '(?<pet>dog|cat)|(?<singer>cher)','I have a pet ${pet}. I like ${singer}''s songs. '
你得到:
I have a pet dog. I like 's songs. I have a pet cat. I like 's songs. I have a pet . I like cher's songs.
......这可能不是你所希望的。
相反,您必须为每个项目进行匹配:
'dogcatcher' -replace '(?<pet>dog|cat)','I have a pet ${pet}. ' -replace '(?<singer>cher)', 'I like ${singer}''s songs. '
......得到:
I have a pet dog. I have a pet cat. I like cher's songs.
更复杂的解决方案
将此重新带回您的场景,您实际上并未使用捕获的值;相反,你希望用新值替换它们所在的空间。为此,您只需要这样:
$input = 'I''m running Programmer''s Notepad version 2.4.2.1440, and am a big fan. I also have Chrome v 56.0.2924.87 (64-bit).'
$version1 = 1
$version2 = 3
$version3 = 5
$version4 = 7
$v1Pattern = '(?<=\bv(?:ersion)?\s+)\d+(?=\.\d+\.\d+\.\d+)'
$v2Pattern = '(?<=\bv(?:ersion)?\s+\d+\.)\d+(?=\.\d+\.\d+)'
$v3Pattern = '(?<=\bv(?:ersion)?\s+\d+\.\d+\.)\d+(?=\.\d+)'
$v4Pattern = '(?<=\bv(?:ersion)?\s+\d+\.\d+\.\d+\.)\d+'
$input -replace $v1Pattern, $version1 -replace $v2Pattern, $version2 -replace $v3Pattern,$version3 -replace $v4Pattern,$version4
哪会给:
I'm running Programmer's Notepad version 1.3.5.7, and am a big fan. I also have Chrome v 1.3.5.7 (64-bit).
注意:以上内容可以写成1个班轮,但我已将其分解,以便于阅读。
这利用了正则表达式的外观;一种检查您正在捕获的字符串之前和之后的内容的方法,不包括匹配中的内容。即,当我们选择要替换的内容时,我们可以说&#34;匹配单词版本后出现的数字&#34;不说&#34;替换版本&#34;。
有关这些内容的更多信息:http://www.regular-expressions.info/lookaround.html
您的示例
使上述内容适用于您的示例(即版本可能用逗号或点分隔,除了4组数字之外,它们的格式没有一致性:
$input = @'
#define SOME_MACRO(4, 1, 0, 0)
Version "1.2.3.4"
SomeStruct vs = { 99,99,99,99 }
'@
$version1 = 1
$version2 = 3
$version3 = 5
$version4 = 7
$v1Pattern = '(?<=\b)\d+(?=\s*[\.,]\s*\d+\s*[\.,]\s*\d+\s*[\.,]\s*\d+\b)'
$v2Pattern = '(?<=\b\d+\s*[\.,]\s*)\d+(?=\s*[\.,]\s*\d+\s*[\.,]\s*\d+\b)'
$v3Pattern = '(?<=\b\d+\s*[\.,]\s*\d+\s*[\.,]\s*)\d+(?=\s*[\.,]\s*\d+\b)'
$v4Pattern = '(?<=\b\d+\s*[\.,]\s*\d+\s*[\.,]\s*\d+\s*[\.,]\s*)\d+\b'
$input -replace $v1Pattern, $version1 -replace $v2Pattern, $version2 -replace $v3Pattern,$version3 -replace $v4Pattern,$version4
给出:
#define SOME_MACRO(1, 3, 5, 7)
Version "1.3.5.7"
SomeStruct vs = { 1,3,5,7 }