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
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)
.
(.+?)
?