【问题标题】:Find and replace with PowerShell based on the next line根据下一行使用 PowerShell 查找和替换
【发布时间】:2018-11-20 17:19:50
【问题描述】:

我正在尝试根据我要替换的行的下一行的内容通过 PowerShell 查找和替换。例如以下文本文件:

废话 胡扯 津赞

如果blahblah之后的行是flimflam,则将blahblah替换为new stuff

这是我目前的代码:

$reader = New-Object System.IO.StreamReader($myFile.FullName);
$FileContents=$reader.ReadToEnd()
$reader.Close()


if(the line after "blahblah" == "flimflam") #pseudo code
{
    $FileContents=$FileContents.Replace("blahblah","new stuff")
}

如果下一行不是flimflam,则什么也不做。

我的一个想法是将“blahblah n` flimflam”替换为“新东西”,但我无法让它发挥作用。我想我可能会加入换行符。

【问题讨论】:

  • 我专门在该行添加了#pseudo code 注释,因为它是唯一的伪代码行。
  • 好的,你现在把它放回去了,很抱歉我的编辑部分不是你想要的。
  • 当输入包含两次flimflam(一个接一个)时会发生什么,例如:'blahblah', 'flimflam', 'flimflam', 'zimzam'

标签: powershell replace


【解决方案1】:
  • 虽然您使用System.IO.StreamReader 有效,但使用Get-Content -Raw 将文件完整读入内存通常更容易,作为单行,多行字符串。

    • 如果考虑性能,您仍然可以直接使用 .NET 类型,在这种情况下,[System.IO.File]::ReadAllText($myFile.FullName) 是一个更简单的替代方案。

    • 要明确指定输入文件的编码,请使用Get-Encoding -Encoding <encoding> / [System.IO.File]::ReadAllText($myFile.FullName, <encoding>)

  • [string] 类型的.Replace() 方法仅限于literal 字符串替换,因此不能选择高级匹配,例如将匹配限制为整行

    • 改用 PowerShell 基于正则表达式的-replace 运算符。

    • 为防止与双引号 ("...") 字符串中的 PowerShell 字符串扩展(字符串插值)混淆,通常最好将 -replace单引号 (@987654331) 一起使用@) 字符串,PowerShell 将其视为文字,因此您可以专注于字符串中的 regex 构造。

PS> $FileContents -replace '(?m)^blahblah(?=\r?\nflimflam$)', 'new stuff'
new stuff
flimflam
zimzam
  • (?m) 使用内联选项m(多行)使锚点^ / $ 匹配每一行的开始/结束(而不是字符串作为一个整体)。

  • (?=...) 是一个前瞻断言,它在匹配时不将匹配部分包含在整个匹配中,因此不会被替换。

  • \r?\n 是一种与平台无关的方式来匹配 换行符 序列/字符:Windows 上的 CRLF (\r\n),Unix 上的 LF-only (\n) -像平台。

【讨论】:

    【解决方案2】:

    使用带有positive lookahead 的正则表达式,您可以在不知道该行内容的情况下替换上一行:

    (Get-Content .\SO_53398250.txt -raw) -replace "(?sm)^[^`r`n]+(?=`r?`nflimflam)","new stuff"|
     Set-Content .\SO_53398250_2.txt
    

    参见regex101.com 上解释的正则表达式(使用不同的转义 `n => \n)

    【讨论】:

      【解决方案3】:

      这个问题要求尽可能多地支持流式传输的可重用 cmdlet...

      Replace-String

      Function Replace-String {
          [CmdletBinding()][OutputType([String[]])]Param (
              [String]$Match, [String]$Replacement, [Int]$Offset = 0,
              [Parameter(ValueFromPipeLine = $True)][String[]]$InputObject
          )
          Begin {
              $Count = 0
              $Buffer = New-Object String[] ([Math]::Abs($Offset))
          }
          Process {
              $InputObject | ForEach-Object {
                  If ($Offset -gt 0) {
                      If ($Buffer[$Count % $Offset] -Match $Match) {$Replacement} Else {$_}
                  } ElseIf ($Offset -lt 0) {
                      If ($Count -ge -$Offset) {If ($_ -Match $Match) {$Replacement} Else {$Buffer[$Count % $Offset]}}
                  } Else {
                      If ($_ -Match $Match) {$Replacement} Else {$_}
                  }
                  If ($Offset) {$Buffer[$Count++ % $Offset] = $_}
              }
          }
          End {
              For ($i = 0; $i -gt $Offset; $i--) {$Buffer[$Count++ % $Offset]}
          }
      }
      

      语法

      $InputObject | Replace-String [-Match] <String to find>
                                    [-Replacement] <Replacement string to use>
                                    [-Offset] <Offset relative to the matched string>
      

      示例:

      替换找到的字符串:

      'One', 'Two', 'Three', 'Four', 'Five' | Replace-String Three X 0
      One
      Two
      X
      Four
      Five
      

      替换找到的字符串之前的字符串:

      'One', 'Two', 'Three', 'Four', 'Five' | Replace-String Three X -1
      One
      X
      Three
      Four
      Five
      

      替换找到的字符串之前的第二个字符串:

      'One', 'Two', 'Three', 'Four', 'Five' | Replace-String Three X -2
      X
      Two
      Three
      Four
      Five
      

      替换找到的字符串后面的字符串:

      'One', 'Two', 'Three', 'Four', 'Five' | Replace-String Three X 1
      One
      Two
      Three
      X
      Five
      

      替换找到的字符串之后的第二个字符串:

      'One', 'Two', 'Three', 'Four', 'Five' | Replace-String Three X 2
      One
      Two
      Three
      Four
      X
      

      替换包含T的(两个)字符串之前的字符串:

      'One', 'Two', 'Three', 'Four', 'Five' | Replace-String T X -1
      X
      X
      Three
      Four
      Five
      

      替换包含T的(两个)字符串之后的字符串:

      'One', 'Two', 'Three', 'Four', 'Five' | Replace-String T X 1
      One
      Two
      X
      X
      Five
      

      具体问题:

      'blahblah', 'flimflam','zimzam' | Replace-String 'flimflam' 'new stuff' -1
      new stuff
      flimflam
      zimzam
      

      参数

      -InputObject &lt;String[]&gt;(来自管道)
      要匹配和替换的字符串流

      -Match &lt;String&gt;
      流中要匹配的字符串。
      请注意,-Match 运算符用于此参数,这意味着它支持正则表达式。如果整个字符串需要匹配,请使用start line and end line anchors,例如:-Match '^Three$'

      -Replacement &lt;String&gt;
      用于替换目标的字符串。

      -Offset &lt;Int&gt; = 0
      相对于要替换的匹配字符串的字符串偏移量。默认为0,意思是:替换匹配的字符串。

      背景

      有关此 cmdlet 编程的一点背景知识:

      • Input Processing MethodsBegin {...}Process {...}End {...} 用于通过 cmdlet 尽可能快地传递字符串并将它们释放到管道中的下一个 cmdlet。这个 cmdlet 是为中间管道(例如Get-Content $myFile | Replace-String A B 1 | ...)。从管道中利用:
        • 避免使用括号(例如:($List) | Replace-String A B
        • 避免分配输出(例如:$Array = ... | Replace-String A B
        • 避免读取整个内容的参数(如Get-Content -Raw
      • 如果这两种情况(替换后面 - 使用负偏移量 - 或前面 - 使用正偏移量 -),则需要偏移大小的缓冲区 ($Buffer = New-Object String[] ([Math]::Abs($Offset)))
      • 为了加快处理速度,脚本循环通过缓冲区 ($Buffer[$Count % $Offset]) 而不是移动包含的项目
      • If ($Count -ge -$Offset) {... 将保存输入字符串的第一个数量(等于偏移量),因为它只能在以后确定是否需要替换输入字符串
      • 最后 (End {...),如果 $Offset 为负数,则缓冲区(包含其余输入字符串)将被释放。换句话说,负偏移量(例如-offset -$n)将缓冲$n 字符串并导致输出在输入流后面运行$n 字符串

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2020-03-13
        • 1970-01-01
        • 2014-09-09
        • 1970-01-01
        • 2013-02-23
        • 2019-03-18
        相关资源
        最近更新 更多