什么构成了Powershell中Select-String方法的"line"?



我希望Select-String考虑rn(回车+换行符)作为Powershell中一行的结尾。

但是,如下所示,abc匹配整个输入:

PS C:Toolshashcat> "abc`r`ndef" | Select-String -Pattern "abc"
abc
def

如果我将字符串分成两部分,那么Select-String的行为符合我的预期:

PS C:Toolshashcat> "abc", "def" | Select-String -Pattern "abc"
abc

如何为Select-String提供一个行以rn结尾的字符串,然后使此 cmdlet 仅返回包含匹配项的字符串?

  • Select-String对每个(按需字符串化[1])输入对象进行操作。

  • 多行字符串(如"abc`r`ndef")是单个输入对象。

    • 相比之下,"abc", "def"是一个包含两个元素的字符串数组,作为两个输入对象传递。
  • 若要确保单独传递多行字符串的,请使用 PowerShell 的-split运算符将字符串拆分为行数组"abc`r`ndef" -split "`r?`n"

    • (?使`r可选,以便正确处理仅`n(仅 LF,Unix 样式)行尾。

总之:

"abc`r`ndef" -split "`r?`n" | Select-String -Pattern "abc"

等效的,使用带有正则表达式 (regex) 转义序列的 PowerShell 字符串文本(-split的 RHS 是正则表达式):

"abc`r`ndef" -split 'r?n' | Select-String -Pattern "abc"
不幸的是

Select-String文档讨论了对文本的操作,因为真正的操作单元是输入对象- 正如我们所看到的,它们本身可能包含多行。
据推测,这来自通过Get-Contentcmdlet 提供输入对象的典型用例,该 cmdlet 逐行输出文本文件的行。

请注意,Select-String不会直接返回匹配的字符串,而是将它们包装在包含有关匹配的有用元数据的[Microsoft.PowerShell.Commands.MatchInfo]对象中。 然而,即使在那里,行隐喻也存在,因为它是包含匹配字符串.Line属性。


[1] 可选阅读:Select-String如何串化输入对象

如果输入对象还不是字符串,则会将其转换为字符串,尽管可能不是您所期望的方式:

粗略地说,.ToString()方法在每个非字符串输入对象[2]上调用,对于非字符串,该方法与使用PowerShell的默认输出格式获得的表示形式不同(后者是您在控制台打印对象或使用Out-File时看到的表示形式;相比之下,它与字符串插值获得的表示形式相同。在双引号字符串中(当您在"..."中嵌入变量引用或命令时,例如,"$HOME""$(Get-Date)")。

通常,.ToString()只生成对象类型的名称,不包含任何特定于实例的信息;例如,$PSVersionTable字符串化为System.Management.Automation.PSVersionHashTable

# Matches NOTHING, because Select-String sees
# 'System.Management.Automation.PSVersionHashTable' as its input.
$PSVersionTable | Select-String PSVersion 

如果您确实想逐行搜索默认输出格式,请使用以下习惯用法:

... | Out-String -Stream | Select-String ...

但是,请注意,对于非字符串输入,后续处理更可靠,最好通过查询具有Where-Object条件的属性来筛选输入。

也就是说,Select-String需要隐式应用字符串化Out-String -Stream,这是一个强有力的理由,如本 GitHub 功能请求中所述。


[2] 更准确地说,.psobject.ToString()被调用,要么按原样调用,要么 - 如果对象的ToString方法支持IFormatProvider类型参数 - 作为.psobject.ToString([cultureinfo]::InvariantCulture)以获得区域性不变表示形式 - 有关详细信息,请参阅此答案。

"abc`r`ndef"

是一个字符串,如果您在控制台中回显 (Write-Output) 将导致:

PS C:Usersgpunktschmitz> echo "abc`r`ndef"
abc
def

Select-String将回显出"abc"是其中一部分的每个字符串。由于"abc"是字符串的一部分,因此将选择此字符串。

"abc", "def"

是两个字符串的列表。使用此处的Select-String将首先测试"abc",如果模式与"abc"匹配,则测试"def"。由于只有第一个匹配,因此才会被选中。

使用以下命令将字符串拆分为一个列表,并仅选择包含"abc"的元素

"abc`r`ndef".Split("`r`n") | Select-String -Pattern "abc"

基本上Guenther Schmitz先生解释了Select-String的正确用法,但我只想补充几点来支持他的回答。

  1. 我针对这个Select-Stringcmdlet 做了一些逆向工程工作。它位于Microsoft.PowerShell.Utility.dll中。一些相关的代码片段如下,请注意这些是来自逆向工程的代码以供参考,而不是实际的源代码。

    string text = inputObject.BaseObject as string;
    ...
    matchInfo = (inputObject.BaseObject as MatchInfo);
    object operand = ((object)matchInfo) ?? ((object)inputObject);
    flag2 = doMatch(operand, out matchInfo2, out text);
    

    我们可以发现它只是将inputObject视为整个字符串,它不进行任何拆分。

  2. 我在 github 上找不到此 cmdlet 的实际源代码,可能这个实用程序部分还不是开源的。但是我找到了这个Select-String的单元测试.

    $testinputone = "hello","Hello","goodbye"
    $testinputtwo = "hello","Hello"
    

    他们用于单元测试的测试字符串实际上是字符串列表。这意味着他们甚至没有考虑您的用例,很可能它只是为了接受字符串集合的输入而设计的。

  3. 但是,如果我们查看有关Select-String的Microsoft官方文档,我们确实会看到它经常谈论,而它无法识别字符串中的。我个人的猜测是,行的概念仅在 cmdlet 接受文件作为输入时才有意义,在文件类似于字符串列表的情况下,列表中的每一项都表示一行。

希望它能让事情更清楚。

相关内容

  • 没有找到相关文章

最新更新