首页 > 解决方案 > 为什么 powershell -replace 运算符未正确包括捕获组中捕获的所有字符?

问题描述

编辑:

再次查看此问题后,我添加了第二个示例,希望能突出我的困惑被放大的地方,即有一个捕获组通过其索引 (1) 访问,而我期望的值$filecontent巧合地也是 1 .


这个问题表明,在引用其他变量时,可以使用反引号来处理双引号字符串中的捕获组。

如果您需要在替换表达式中引用其他变量(可能),您可以使用双引号字符串并用反引号转义捕获美元

但是,我看到了一些我无法解释的有趣行为。

$VersionReplacementRegex = "(\d+\.)\d+" #capture first digit + dot b/c I want to keep it
$BuildVersionValidationRegex = "\d+\.\d+\.\d+"
    
$VersionData = [regex]::matches("some-18.11.8",$BuildVersionValidationRegex)
$NewVersion = $VersionData[0] #matches 18.11.8

$filecontent = "stuff 1.0.0.0 other stuff" #Get-Content($file)

$filecontent使用链接问题中指定的捕获组替换文本会给出不完整的结果...

$filecontent -replace $VersionReplacementRegex, "`$1$NewVersion" | Write-Host

返回:118.11.8 预期:1.18.11.8

$1但是在and之间添加一个空格$NewVersion会产生不同但同样无益的结果..

$filecontent -replace $VersionReplacementRegex, "`$1 $NewVersion" | Write-Host

返回:1. 18.11.8 捕获的点出现在这里,但不需要的空间也出现。

在这个例子中,结果有些相似,但似乎捕获组一起得到了错误的值。

$NewVersion = 18.11.8
$filecontent = "stuff 5.0.0.0 other stuff"
$filecontent -replace "(\d+\.)\d+", "`$1$NewVersion" | Write-Host

# returns: 118.11.8
# expected: 5.18.11.8

在替换字符串中添加空格会返回:5. 18.11.8

那么,我错过了什么,还是有更好的方法来做到这一点?

标签: powershell

解决方案


从过去的经验来看,在对该问题的评论中提供了关键指针的PetSerAl不会回来发布答案。

tl;博士

如果使用引用捕获组PowerShell-replace变量的替换操作数,请使用语法,例如 "`${<ndx>}${<PsVar>}",其中<ndx>是捕获组的索引,并且<PsVar>是 PowerShell 变量的名称;注意第`一个之前$

PS> $var = '2'; 'foo' -replace '(f)', "[`${1}$var]"
[f2]oo # OK, -replace saw '${1}2'

如果您忽略使用{...}来消除捕获组索引的歧义,则替换会发生故障,因为插值后的字符串值会有效地引用不同的索引:
-replacethen sees [$12],由于使用 index 引用不存在的捕获组12,因此会保持原样:

PS> $var = '2'; 'foo' -replace '(f)', "[`$1$var]"
[$12]oo # !! -replace saw '$12', i.e., a nonexistent group with index 12

将 PowerShell 的字符串扩展(插值)与
-replaceoperator的语法混合起来很棘手
,因为很容易混淆

  • 双引号( ) 字符串中,首先解释字符"..."的是 PowerShell 的通用字符串扩展(字符串插值)功能,其中前缀指的是 (PowerShell)变量,内部指的是整个语句$$$(...)

  • 任何字符串是该扩展的结果,然后-replace运算符解释,其中$-prefixed 标记指的是正则表达式匹配操作的结果,如本答案中所总结的那样。

  • 请注意,这些$解释层是完全不相关的,两者都使用印记的事实$是偶然的。

所以:

  • 如果您的替换操作数不需要字符串扩展,即如果不需要引用 PowerShell变量表达式,请务必使用单引号字符串 ( '...'),这样 PowerShell 的字符串扩展就不会发挥作用:

     PS> 'foo' -replace '(f)', '[$1]'
     [f]oo  # OK - if you had used "[$1]" instead, the output would be '[]oo',
            # because $1 is then interpreted as a *PowerShell variable*.
    
  • 如果您确实需要涉及字符串扩展

    • 前缀$字符。应该通过 to -replace with`

      • `(反引号)是 PowerShell 的通用转义字符,在"..."字符串中它用于指示下一个字符将按字面意思获取;放在 a 之前$,它会抑制该标记的字符串插值;例如,"I'm `$HOME"产生 literal I'm $HOME,即变量引用展开。
    • 为了消除对捕获组的引用的歧义,例如$1将它们括在{...}- 例如,${1}

      • 请注意,您可能还需要使用PowerShell变量名{...}来消除歧义;例如必须是为了成功引用变量。"$HOME1""${HOME}1"$HOME
      • 此外,这不仅仅是关于捕获组索引命名捕获组也可能产生歧义;基于in"..."的替换操作数,始终使用{...}捕获组索引/名称(和 PS 变量)是一个好习惯。
    • 如果有疑问,请自行输出替换操作数,以检查-replace最终会看到什么。

      • 在上面的示例"[`$1$var]"中,应用字符串插值步骤的自身输出会使问题更加明显:[$12]

为了说明后一点:

PS> $var = '2'; 'foo' -replace '(f)', "[`$1$var]"
[$12]oo  # !! $1 wasn't recognizes as the 1st capture group.

问题是-replace,在字符串扩展之后,将[$12]其视为替换操作数,并且由于没有带有 index 的捕获组12,所以它保持原样。

附上捕获组号{...}解决了这个问题:

PS> $var = '2'; 'foo' -replace '(f)', "[`${1}$var]"
[f2]oo  # OK

推荐阅读