【问题标题】:Select-String not working on piped object using Out-StringSelect-String 不适用于使用 Out-String 的管道对象
【发布时间】:2021-01-20 10:57:28
【问题描述】:

我正在执行一个返回一堆数据的 API 请求。在尝试使用 Select-String 搜索它时,它只会吐出存储在变量中的整个值。这是一个互联网服务器,我称之为 api。

$return = Invoke-RestMethod -Method GET -Uri $uri -Headers @{"authorization" = $token} -ContentType "application/json" 
$file = $return.data
$file | Out-String -Stream | Select-String -Pattern "word"

这将返回 $file 的整个值。打印 $file 看起来与管道输出相同。为什么这不起作用?

$file.Gettype 说它是一个 system.object,另一个答案说使用 Out-String,但有些东西不起作用。

$file.Gettype
IsPublic IsSerial Name                                     BaseType                   
-------- -------- ----                                     --------                   
True     True     String                                   System.Object 

【问题讨论】:

  • 您的论点中存在一些漏洞,表明它没有按您预期的方式工作。请创建一个minimal reproducible example 并将 $return.data 的示例添加到问题中,以便我们可以证明它确实有效(或无效)。

标签: powershell


【解决方案1】:

为了补充 iRon7's helpful answerOut-String-Stream 开关的精确逻辑,从 PowerShell 7.1 开始:

Out-String 与其他Out-* cmdlet(如Out-File)一样,使用PowerShell's rich output-formatting system 生成其输入对象的人性化表示。

  • 没有 -StreamOut-String 只会产生单行(通常)多行字符串 .

  • 使用-Stream逐行输出行为通常发生 - 除了对于碰巧是多行字符串的输入对象,它们按原样输出。

对于所谓的in-band数据类型,-Stream的工作方式如下,真正实现了逐行输出:

  • 输入对象由 PowerShell 丰富的格式化系统格式化,构成结果表示的行然后一个接一个地输出

带外数据类型在格式化系统之外单独格式化,只需调用它们的 .NET .ToString() 方法。

简而言之:表示单个值的数据类型是带外数据类型,除了[string]带外数据类型还包括[char]和各种(标准)数字类型,例如[int][long][double],...

[string]only 本身可以导致 multiline 表示的带外类型,因为在字符串上调用 .ToString() 是有效的 -返回字符串本身的操作 - 无论是单行还是多行。

因此:

  • 任何字符串——尤其是多行字符串——都按原样作为一个整体输出,并且将其拆分为单独的行需要显式操作;例如(请注意,正则表达式 \r?\n 匹配 Windows 样式的 CRLF 和 Unix 样式的 LF-only 换行符):

    "line 1`nline 2`nline 3" -split '\r?\n' # -> 'line 1', 'line 2', 'line 3'
    
  • 如果您的输入对象是带内对象和(总是带外)多行字符串的混合,您可以将Out-String -Stream-split 结合使用;例如:

    ((Get-Date), "line 1`nline 2`nline 3" | Out-String -Stream) -split '\r?\n' 
    

【讨论】:

    【解决方案2】:

    仔细检查后,我怀疑您的问题来自 Out-String 文档中的歧义:

    -Stream

    表示 cmdlet 为每个 line 发送一个单独的字符串 输入对象。默认情况下,每个对象的字符串都是累加的 并作为单个字符串发送。

    line 这个词应该读作item

    要将原始字符串拆分为单独的行,您需要使用以下命令拆分字符串:

    $Lines = $return.data -split [Environment]::NewLine
    

    请注意,这假设您的数据在换行时使用与您正在使用的系统相同的字符。如果不是这种情况,您可能需要使用正则表达式拆分行,例如:

    $Lines = $return.data -split "`r*`n"
    

    那么-Stream参数有什么作用呢?

    它为输入对象的每个 item 发送一个单独的字符串。
    在此定义中,为 possible 复数 input objectS 使用单数名称也是已知的常见 PowerShell 最佳实践。

    意思是如果你使用上面定义的$Lines变量(或类似$Lines = Get-Content .\File.json),输入对象“$Lines”是字符串的集合:

    $Lines.GetType().Name
    String[]
    

    如果您将此流式传输到Out-String,它将(默认情况下)连接所有项目并返回单个字符串:

    ($Lines | Out-String).GetType().Name
    String
    

    相比之下,如果您使用-Stream 参数,它会将$Lines 集合中的每个分隔项直接传递给下一个cmdlet:

    ($Lines | Out-String -Stream).GetType().Name
    Object[]
    

    我为此创建了一个文档问题:#7133 "line" should be "item"

    注意:

    一般来说,it is a bad practice to peek and poke directly into a serialized string (包括Json)使用字符串 方法和/或 cmdlet(如 Select-String)。相反,您应该使用 相关的解析器(例如 ConvertFrom-Json) 用于搜索和替换,这将导致更简单的语法 并且通常会处理已知问题和陷阱。

    【讨论】:

    • 您的解释非常有用,解决了我的问题。当 API 返回数据时,每个新行都有一个换行符。因此,仅使用 "`n" 进行拆分解决了 Select-String 仅返回一个匹配项或根本不返回匹配项的问题。
    • 很好的答案,但每个项目的解释不正确 - 这是关于带内与带外格式的 - 请参阅我的答案;另外,我建议将"`r?`n"'\r?\n' 作为正则表达式。
    【解决方案3】:

    Select-String 输出 Microsoft.PowerShell.Commands.MatchInfo 对象。在我看来,输出以某种方式通过 PS 引擎或其他东西来突出显示您的匹配,但最终它确实打印了整个匹配的字符串。

    您应该检查Select-String 提供的对象的成员,如下所示:

    $file | Out-String -Stream | Select-String -Pattern "word" | Get-Member
    
       TypeName: Microsoft.PowerShell.Commands.MatchInfo
    
    Name               MemberType Definition
    ----               ---------- ----------
    ...
    Matches            Property   System.Text.RegularExpressions.Match[] Matches {get;set;}
    ...
    

    您感兴趣的是Matches 属性。它包含一堆关于比赛的信息。要准确提取您想要的内容,请查看MatchesValue 属性:

    ($file | Out-String -Stream | Select-String -Pattern "word").Matches.Value
    
    word
    

    另一种方式:

    $file | Out-String -Stream | Select-String -Pattern "word" | ForEach-Object {$_.Matches} | Select-Object -Property Value
    
    Value
    -----
    word
    

    或者

    $file | Out-String -Stream | Select-String -Pattern "word" | ForEach-Object {$_.Matches} | Select-Object -ExpandProperty Value
    
    word
    

    【讨论】:

    • 这似乎只找到了一个匹配项,而不是所有匹配项。使用 ).matches.value 或与 ForEach-Object 一起使用。如果我将数据保存到文件中,并从文件中读取它,它工作得很好。只是不使用变量管道。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多