【问题标题】:PowerShell: Select line preceding a match -- Select-String -Context issue when using input string variablePowerShell:选择匹配前的行——使用输入字符串变量时的 Select-String -Context 问题
【发布时间】:2017-11-24 16:58:15
【问题描述】:

我需要在多行字符串变量的匹配之前返回一行。

当对输入使用字符串变量时,Select-String 似乎认为整个字符串已匹配。因此,上下文属性在字符串的任一端“外部”并且为空。

考虑下面的例子:

$teststring = @"
line1
line2
line3
line4
line5
"@

Write-Host "Line Count:" ($teststring | Measure-Object -Line).Lines #verify PowerShell does regard input as a multi-line string (it does)

Select-String -Pattern "line3" -InputObject $teststring -AllMatches -Context 1,0 | % {
$_.Matches.Value #this prints the exact match
$_.Context #output shows all context properties to be empty 
$_.Context.PreContext[0] #this would ideally output first line before the match
$_.Context.PreContext[0] -eq $null #but instead is null
}

我是不是误会了什么?

匹配“line3”时返回“line2”的最佳方式是什么?

谢谢!

编辑: 我忽略说明的其他要求: 需要为不确定长度的字符串提供所有匹配行上方的行。 EG 在下面搜索“line3”时,我需要返回“line2”和“line5”。

line1
line2
line3
line4
line5
line3
line6

【问题讨论】:

    标签: string powershell match select-string


    【解决方案1】:

    Select-String 对输入的 数组 进行操作,因此 您必须提供一个 而不是 单个多行字符串 >行数组让-Context和-AllMatches按预期工作:

    $teststring = @"
    line1
    line2
    line3
    line4
    line5
    line3
    line6
    "@
    
    $teststring -split '\r?\n' | Select-String -Pattern "line3" -AllMatches -Context 1,0 | % {
      "line before:  " + $_.Context.PreContext[0]
      "matched part: " + $_.Matches.Value  # Prints the what the pattern matched
    }
    

    这会产生:

    line before:  line2
    matched part: line3
    line before:  line5
    matched part: line3
    
    • $teststring -split '\r?\n' 将多行字符串拆分为行数组:

      • 注意:您的 here-document 使用的换行序列(仅 LF 与 CRLF)取决于封闭的脚本文件;正则表达式 \r?\n 处理任一样式。
    • 请注意,使用 管道 提供Select-String 的输入至关重要;如果您使用-InputObject,则该数组将被强制转换为单个字符串。


    Select-String 很方便,但慢。
    特别是对于已经在内存中的单个字符串,使用 .NET Framework 的 [Regex]::Matches() 方法的解决方案会执行得更好,尽管它更复杂。

    请注意,PowerShell 自己的 -match 和 -replace 运算符构建在同一个 .NET 类上,但并未公开其所有功能; -match - 在自动 $Matches 变量中报告捕获组 - 这里不是一个选项,因为它只返回 1 匹配项。

    以下方法与mjolinor's answer 回答中的方法基本相同,但修正了几个问题[1]。

    # Note: The sample string is defined so that it contains LF-only (\n)
    #       line breaks, merely to simplify the regex below for illustration.
    #       If your script file use LF-only line breaks, the 
    #       `-replace '\r?\n', "`n" call isn't needed.
    $teststring = @"
    line1
    line2
    line3
    line4
    line5
    line3
    line6
    "@ -replace '\r?\n', "`n" 
    
    [Regex]::Matches($teststring, '(?:^|(.*)\n).*(line3)') | ForEach-Object { 
      "line before:  " + $_.Groups[1].Value
      "matched part: " + $_.Groups[2].Value
    }
    
    • Regex (?:^|(.*)\n).*(line3) 使用 2 个捕获组 ((...)) 来捕获要匹配的行的(匹配部分)和之前的行((?:...) 是一个辅助非-捕获优先级所需的组):

      • (?:^|(.*)\n) 匹配字符串的开头 (^) 或 (|) 任何 - 可能为空 - 非换行符 (.*) 后跟换行符 (\n) 的序列;这确保了当前行没有 时也能找到要匹配的行(即,要匹配的行是 first 行)。
      • (line3) 是定义要匹配的行的组;它前面有.* 以匹配问题中的行为,其中模式line3 被发现,即使它只是一行的part。
        • 如果您只想匹配 完整 行,请改用以下正则表达式:
          (?:^|(.*)\n)(line3)(?:\n|$)
    • [Regex]::Matches() 查找 所有 匹配项并将它们作为System.Text.RegularExpressions.Match 对象的集合返回,然后ForEach-Object cmdlet 调用可以对其进行操作以提取捕获组匹配项( $_.Groups[<n>].Value)。


    [1] 撰写本文时:
    - 没有必要匹配 两次 - 封闭的 if ($teststring -match $pattern) { ... } 是不必要的。
    - 不需要内联选项 (?m),因为 . 不 匹配换行符默认情况下。
    - (.+?) 仅捕获 非空行(并且不需要非贪婪量词 ?)。
    - 如果感兴趣的行是 first 行 - 即,如果没有行 before,则不会匹配。

    【讨论】:

      【解决方案2】:

      您可以使用带有-match 运算符的多行正则表达式:

      $teststring = @"
      line1
      line2
      line3
      line4
      line5
      line3
      line6
      "@
      
      $pattern = 
      @'
      (?m)
      (.+?)
      line3
      '@
      
      
      if ($teststring -match $pattern)
        { [Regex]::Matches($teststring,$pattern) |
          foreach {$_.groups[1].value} }
      

      【讨论】:

        猜你喜欢
        • 2016-11-11
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-11-24
        • 1970-01-01
        相关资源
        最近更新 更多