【问题标题】:Having some trouble with Powershell -replace and Environment Variables from Batch File批处理文件中的 Powershell -replace 和环境变量有一些问题
【发布时间】:2021-12-16 17:36:16
【问题描述】:

我终于缩小了问题的范围,但需要帮助理解为什么。

从批处理文件中调用 Powershell,以下方法可以替换文件中的字符串:

Powershell -Command "$foobar=[IO.File]::ReadAllText("$Env:File") ; 
$foobarred= $foobar -replace '(?<foo>.*Backup.).*.(?<bar>..Backup.)', '${foo}Enabled${bar}' ;
 [IO.File]::WriteAllText("$Env:File", $foobarred); "

现在如果我尝试这个,命令会失败:


Powershell -Command "$foobar=[IO.File]::ReadAllText("$Env:File") ; 
$foobarred= $foobar -replace '(?<foo>.*$Env:StrStart.).*.(?<bar>..$Env:StrEnd.)', '${foo}$Env:StrVal${bar}' ;
 [IO.File]::WriteAllText("$Env:File", $foobarred); "

如果我使用从 Batch 传入的变量,它每次都会失败。如果我在命令中使用 PlainText 代替替换值,它就可以正常工作。为什么会这样?

【问题讨论】:

  • 您在这里使用的是动态创建的正则表达式,并且您没有对任何变量进行正则表达式转义。您需要显示您正在使用的变量值,理想情况下也需要使用示例文件。 “每次都失败”信息太少,无法继续。
  • @Tomalak - 变量在 GOTO 循环中生成。总是有一个开始标签的模式:(.*SomeText.) 然后是我要替换的文本 .*. 然后是结束标签:(..SomeText.) 我有脚本和批处理文件的全部范围,这一切都实现了,但它非常罗嗦。不幸的是,我不允许从我的工作 PC 上提供我正在使用的任何信息。变量将是“StringStart”、“StringVal”和“StringEnd”——“StringVal”将是我要插入的文本。
  • @Tomalak,您希望我尝试重新编辑问题以包含所有以前包含的信息,以便您了解完整的上下文吗?我认为模组在 S.O.元方面将用编辑次数烧掉这个问题
  • 再看一遍,我意识到我把工作版本与非工作版本放在了一起。工作版本没有任何变量 - 只有 ${foo} 和 ${bar} - 命名的 Regex BackReferences。变量中的字符串不会有任何特殊字符,可能 $StrVal 除外。 . .在这种情况下,我知道我应该使用 [regex]::escape($StrVal)。 . .我认为我的部分问题是我也在尝试在正则表达式的模式部分中使用变量,但它正在将它们作为元 $ 拾取 - 我接近了吗?
  • 不要将问题的解决方案添加到问题中。而是将其发布为答案,如果它解决了您的问题,您也可以将其标记为正确答案。

标签: powershell batch-file


【解决方案1】:

在@Gerhard 的建议下,将我的发现作为答案添加到了战斗中,但最终还是让@Tomalak 获得了整体更好的答案。

为了将变量接受到匹配模式和替换模式中,您必须连接字符串(类似于在 Visual Basic 中的操作方式)。

参考如下:

为了便于阅读,我已将命令分成多行 - 如果您使用此命令,请将其放在一行中。

重要 - 使用此功能时,如果在批处理文件中使用它,请务必删除换行符。 另外 - 请注意 - 我遇到过一些情况,即使使用 Set-Content 也可以更改文件的编码。我更喜欢下面提供的辅助解决方案。

Powershell -Command "$pattern= '(?<RangeStart>.*' + [regex]::Escape($Env:StrStart) + '.).*.(?<RangeEnd>..' + [regex]::Escape($Env:StrEnd) + '.)' ;
$repl= '${RangeStart}' + $Env:StrVal + '${RangeEnd}' ;
$fil2parse=(Get-Content $Env:FileTOParse) -replace $pattern, $repl | Set-Content $Env:FileTOParse; "

这个解决方案也很有效,但我在更改编码方面遇到的问题要少得多。

Powershell -Command "$pattern= '(?<RangeStart>.*' + [regex]::Escape($Env:StrStart) + '.).*.(?<RangeEnd>..' + [regex]::Escape($Env:StrEnd) + '.)' ;
$repl= '${RangeStart}' + $Env:StrVal + '${RangeEnd}' ;
$fil2parse=[IO.File]::ReadAllText("$Env:FileTOParse") ;
$filParsed= $fil2parse -replace $pattern, $repl ;
[IO.File]::WriteAllText("$Env:FileTOParse", $filParsed); "

【讨论】:

  • Get-ContentSet-Content 实际上需要知道文件编码。如果您不指定它们,那总是等待发生的错误,尤其是对于没有 BOM 的 UTF-8 文件。不要把它留给随机机会,固定-Encoding 参数。
【解决方案2】:

我的观察结果是

  • 动态构建正则表达式时需要[regex]::Escape()任意值。
  • PowerShell 不会在单引号字符串中进行任何字符串插值,因此 '${foo}$Env:StrVal${bar}' 之类的内容不会按您希望的方式工作。

我会使用以下命令:

(Get-Content "filename" -Raw) -replace (
    '(.*' + [regex]::Escape("start string") + '.).*.(..' + [regex]::Escape("end string") + '.)'
),(
    '$1' + "replacement string" + '$2'
) | Set-Content "filename"

从批处理文件中调用 ad-hoc 如下(压缩到一行):

@echo off
setlocal

set "FILENAME=filename"
set "START=start string"
set "END=end string"
set "REPLACEMENT=replacement string"

set "PSCMD=(gc $Env:FILENAME -Raw) -replace ('(.*' + [regex]::Escape($Env:START) + '.).*.(..' + [regex]::Escape($Env:END) + '.)'),('$1' + $Env:REPLACEMENT + '$2') | sc $Env:FILENAME"

powershell -NoLogo -Command "&{%PSCMD%}"

但这非常难以维护。

我建议编写一个 .ps1 文件并传递命名参数,而不是杂耍环境变量。

# MyReplace.ps1
param(
    [string]$Filename,
    [string]$Start,
    [string]$End,
    [string]$Replacement
)
$ErrorActionPreference = "Stop"

$content = Get-Content $Filename -Raw
$content = $content -replace ('(.*' + [regex]::Escape($Start) + '.).*.(..' + [regex]::Escape($End) + '.)'),('$1' + $Replacement + '$2')
$content | Set-Content $Filename

分批

powershell -NoLogo -File MyReplace.ps1 -Filename "filename" -Start "start string" -End "end string" -Replacement "replacement string"

这对我来说似乎更易于管理。

【讨论】:

  • 我终于找到了解决方案,和你在这里发布的非常接近。不幸的是,我们被禁止使用 PS1 脚本,所以这不是一个选项。参考您的第一部分答案,这与我发现最终起作用的非常相似:Powershell -Command "$pattern= '(?&lt;RangeStart&gt;.*' + [regex]::Escape($Env:StrStart) + '.).*.(?&lt;RangeEnd&gt;..' + [regex]::Escape($Env:StrEnd) + '.)' ; $repl= '${RangeStart}' + $Env:StrVal + '${RangeEnd}' ; $fil2parse=(Get-Content $Env:FileTOParse) -replace $pattern, $repl | Set-Content $Env:FileTOParse; "
  • @k1dfr0std 是的,您的解决方案和我的解决方案几乎相同。虽然我不明白限制 PS1 文件的原因,但允许使用任意 PowerShell 代码的 BAT 文件,我不会争论。 :)
  • 我知道吗?!?我也不明白——这不是我们的选择。 . .不得不为批处理编程学习一堆晦涩难懂的知识很有趣
  • @k1dfr0std 你会惊讶于人们想出的东西。我编写了极其复杂的批处理程序,并且一直在发现我不知道的东西是可能的。
  • @k1dfr0std 我一直在使用ansicon,因为......永远。 :)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-01-08
  • 2018-08-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多