4

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

似乎在对输入使用字符串变量时 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
4

2 回答 2

7

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 的答案基本相同,但纠正了几个问题[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) 的序列;这确保了在没有前一行时也可以找到要匹配的行(即,要匹配的行是第一行)。
    • (line3)是定义要匹配的行的组;它前面是.*匹配问题中的行为,line3即使它只是一行的一部分,也可以找到模式。
      • 如果您只想匹配行,请改用以下正则表达式:
        (?:^|(.*)\n)(line3)(?:\n|$)
  • [Regex]::Matches()查找所有匹配项并将它们作为System.Text.RegularExpressions.Match对象集合返回,ForEach-Object然后 cmdlet 调用可以对其进行操作以提取捕获组匹配项 ( $_.Groups[<n>].Value)。


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

于 2017-06-21T19:23:39.743 回答
1

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

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

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


if ($teststring -match $pattern)
  { [Regex]::Matches($teststring,$pattern) |
    foreach {$_.groups[1].value} }
于 2017-06-21T18:12:52.747 回答