【问题标题】:What constitutes a "line" for Select-String method in Powershell?什么构成 Powershell 中 Select-String 方法的“线”?
【发布时间】:2018-04-22 09:57:02
【问题描述】:

我希望 Select-String 考虑 \r\n(回车 + 换行)在 Powershell 中的行尾。

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

PS C:\Tools\hashcat> "abc`r`ndef" | Select-String -Pattern "abc"

abc
def

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

PS C:\Tools\hashcat> "abc", "def" | Select-String -Pattern "abc"

abc

如何给Select-String 一个行以\r\n 终止的字符串,然后让这个cmdlet 只返回那些包含匹配项的字符串?

【问题讨论】:

标签: powershell split select-string


【解决方案1】:
  • Select-String 对每个(按需字符串化[1]输入对象进行操作。

  • 一个多行字符串,例如"abc`r`ndef"是一个输入对象。

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

    • ? 使 `r 可选,以便也正确处理 `n-only(LF-only,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-Content cmdlet 提供输入对象的用例,它一行一行地输出文本文件的行

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


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

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

简单地说,.ToString() 方法是在每个非字符串输入对象上调用的[2] ,对于非字符串,它 not 与您使用 PowerShell 的 默认输出格式 获得的表示相同(后者是您在例如,将对象打印到控制台或使用Out-File);相比之下,它与您在双引号字符串中使用 字符串插值 获得的相同表示(当您在"..." 中嵌入变量引用或命令时,例如,@ 987654346@ 或 "$(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 字符串化,如this GitHub feature request 中所述。 p>


[2] 更准确地说,.psobject.ToString() 被按原样调用,或者 - 如果对象的 ToString 方法支持 IFormatProvider 类型的参数 - 作为 .psobject.ToString([cultureinfo]::InvariantCulture) 来获得culture-invariant 表示 - 请参阅 this answer 了解更多信息。

【讨论】:

    【解决方案2】:
    "abc`r`ndef"
    

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

    PS C:\Users\gpunktschmitz> 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"
    

    【讨论】:

      【解决方案3】:

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

      1. 我针对这个Select-String cmdlet 做了一些逆向工程工作。它位于 Microsoft.PowerShell.Utility.dll 中。部分相关代码sn-ps如下,注意这些是逆向工程的代码供参考,并非实际源代码。

        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-Stringunit test

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

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

      3. 但是,如果我们查看 Microsoft 的 official document 关于 Select-String,我们确实看到它经常谈论 line,但它无法识别 line 在一个字符串中。我个人的猜测是 line 的概念只有在 cmdlet 接受文件作为输入时才有意义,如果文件就像一个字符串列表,列表中的每个项目代表一行。

      希望它能让事情更清楚。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2017-08-06
        • 2013-08-13
        • 1970-01-01
        • 2021-12-19
        • 2021-10-29
        • 2015-11-22
        • 2017-12-16
        相关资源
        最近更新 更多