PowerShell:选择匹配项之前的行-使用输入字符串变量时Select-String -Context问题 [英] PowerShell: Select line preceding a match -- Select-String -Context issue when using input string variable
问题描述
我需要在多行字符串变量上返回匹配项之前的一行.
I need return a line preceeding a match on a multi-line string variable.
似乎在将字符串变量用于输入Select-String时,会将整个字符串视为已匹配.因此,Context属性位于字符串的任一端外部",并且为null.
It seems when using a string variable for the input Select-String considers the entire string as having matched. As such the Context properties are "outside" either end of the string and are null.
考虑以下示例:
$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"的最佳方法是什么?
What is the best way to return "line2" when matching for "line3"?
谢谢!
我忽略声明的其他要求: 需要在所有匹配的行上方提供不确定长度的字符串. EG在下面搜索"line3"时,我需要返回"line2"和"line5".
Additional requirements I neglected to state: Needs to provide the line above ALL matched lines for a string of indeterminate length. EG when searching the below for "line3" I need to return "line2" and "line5".
line1
line2
line3
line4
line5
line3
line6
推荐答案
Select-String
对输入的数组进行操作,因此而不是单行,多行字符串,您必须提供行数组 ,以使-Context
和-AllMatches
能够按预期工作:
Select-String
operates on arrays of input, so rather than a single, multiline string you must provide an array of lines for -Context
and -AllMatches
to work as intended:
$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'
将多行字符串分成行数组:$teststring -split '\r?\n'
splits the multi-line string into an array of lines:- 注意:您的此处文档使用的换行符顺序(仅LF相对于CRLF)取决于随附的脚本文件; regex
\r?\n
处理这两种样式.
- Note: What line-break sequences your here-document uses (LF-only vs. CRLF) depends on the enclosing script file; regex
\r?\n
handles either style.
请注意,使用管道提供
Select-String
的输入至关重要.如果使用-InputObject
,则该数组将被强制返回为单个字符串.Note that it is crucial to use the pipeline to provide
Select-String
's input; if you used-InputObject
, the array would be coerced back to a single string.Select-String
很方便,但是慢.
特别是对于已经在内存中的单个字符串,使用.NET Framework的Select-String
is convenient, but slow.
Especially for a single string already in memory, a solution using the .NET Framework's[Regex]::Matches()
method will perform much better, though it is more complex.请注意,PowerShell自己的
-match
和-replace
运算符是基于同一.NET类构建的,但是并未公开其所有功能;请参见-match
-不会在自动$Matches
变量中报告捕获组-在这里不是一个选项,因为它只会返回 1 匹配.Note that PowerShell's own
-match
and-replace
operators are built on the same .NET class, but do not expose all of its functionality;-match
- which does report capture groups in the automatic$Matches
variable - is not an option here, because it only ever returns 1 match.以下内容与 mjolinor的答案答案基本相同,但已纠正了一些问题[1] ].
The following is essentially the same approach as in mjolinor's answer answer, but with several problems corrected[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个捕获组((...)
)捕获要匹配的行(匹配的部分)和之前的行((?:...)
是辅助 non 捕获优先级所需的组):Regex
(?:^|(.*)\n).*(line3)
uses 2 capture groups ((...)
) to capture both the (matching part of) the line to match and the line before ((?:...)
is an auxiliary non-capturing group that is needed for precedence):-
(?:^|(.*)\n)
匹配字符串(^
)或(|
)开头的任何-可能为空的非换行字符序列(.*
)后跟换行符(\n
) ;这样可以确保在前一行没有 的情况下也找到要匹配的行(即,要匹配的行是第一个). -
(line3)
是定义要匹配的行的组;它以.*
开头以匹配问题中的行为,即使仅是行的 part ,也可以在其中找到模式line3
.- 如果只希望匹配 full 行,请使用以下正则表达式代替:
(?:^|(.*)\n)(line3)(?:\n|$)
(?:^|(.*)\n)
matches either the very start of the string (^
) or (|
) any - possibly empty - sequence of non-newline characters (.*
) followed by a newline (\n
); this ensures that the line to match is also found when there is no preceding line (i.e., of the line to match is the first one).(line3)
is the group defining the line to match; it is preceded by.*
to match the behavior in the question, where patternline3
is found even it is only part of a line.- If you want only full lines to match, use the following regex instead:
(?:^|(.*)\n)(line3)(?:\n|$)
[Regex]::Matches()
查找所有 匹配项,并将其作为[Regex]::Matches()
finds all matches and returns them as a collection ofSystem.Text.RegularExpressions.Match
objects, which theForEach-Object
cmdlet call can then operate on to extract the capture-group matches ($_.Groups[<n>].Value
).[1]撰写本文时:
-不需要匹配两次-不需要包含if ($teststring -match $pattern) { ... }
.
-不需要内联选项(?m)
,因为默认情况下.
不会 匹配换行符 .
-(.+?)
仅捕获非空行(并且不需要?
(非贪婪的量词)).
-如果感兴趣的行是第一行-即,如果在之前没有行,则不会匹配.[1] As of this writing:
- There is no need to match twice - the enclosingif ($teststring -match $pattern) { ... }
is unnecessary.
- Inline option(?m)
is not needed, because.
does not match newlines by default.
-(.+?)
captures only nonempty lines (and?
, the non-greedy quantifier, is not needed).
- If the line of interest is the first line - i.e., if there's no line before, it won't be matched.这篇关于PowerShell:选择匹配项之前的行-使用输入字符串变量时Select-String -Context问题的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!
- If you want only full lines to match, use the following regex instead:
- 如果只希望匹配 full 行,请使用以下正则表达式代替:
-
- 注意:您的此处文档使用的换行符顺序(仅LF相对于CRLF)取决于随附的脚本文件; regex