PowerShell:匹配前的 Select 行 - Select-String - 使用输入字符串变量时的上下文问题

PowerShell: Select line preceding a match -- Select-String -Context issue when using input string variable

我需要 return 多行字符串变量匹配前的一行。

似乎在对输入使用字符串变量时 Select-String 认为整个字符串都匹配。因此,上下文属性是 "outside" 字符串的任一端并且为空。

考虑以下示例:

$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" 时 return "line2" 的最佳方法是什么?

谢谢!

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

line1
line2
line3
line4
line5
line3
line6

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

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

$pattern = 
@'
(?m)
(.+?)
line3
'@


if ($teststring -match $pattern)
  { [Regex]::Matches($teststring,$pattern) |
    foreach {$_.groups[1].value} }

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' 将多行字符串拆分成一个行数组:

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


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

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

以下与 答案中的方法基本相同,但更正了几个问题[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
}
  • 正则表达式 (?:^|(.*)\n).*(line3) 使用 2 个捕获组 ((...)) 捕获要匹配的行的(匹配部分)和之前的行 ((?:...)是一个辅助-捕获组,需要优先级):

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


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