【问题标题】:How to stop pipeline filtering (Where-Object) on first match如何在第一次匹配时停止管道过滤(Where-Object)
【发布时间】:2017-09-20 16:44:15
【问题描述】:

我进行了搜索,但还没有找到方法。
我正在从大文件 (~2GB) 中过滤数据。
我使用了Where-Object,当它找到匹配项时,它会继续搜索其他有意义的匹配项。

是否可以在第一场比赛中停止它?

例如(#1)

Get-Process | Where-Object {$_.ProcessName.StartsWith("svchost")}

输出将是:

Handles  NPM(K)    PM(K)      WS(K) VM(M)   CPU(s)     Id ProcessName
-------  ------    -----      ----- -----   ------     -- -----------
    666      38    26928      18672    92             568 svchost
    596      28    11516      16560    92             792 svchost
    425      14     5364       7036    45             832 svchost
    406      17     7032       8416    39            1004 svchost

我想要的是在第一次匹配后返回输出:

Handles  NPM(K)    PM(K)      WS(K) VM(M)   CPU(s)     Id ProcessName
-------  ------    -----      ----- -----   ------     -- -----------
    666      38    26928      18672    92             568 svchost

这是我尝试过的(也适用于 Foreach-Object):

Get-Process | Where-Object {if($_.ProcessName.StartsWith("svchost")){return $_}}
Get-Process | Where-Object {if($_.ProcessName.StartsWith("svchost")){return $_;break;}}    
Get-Process | ForEach-Object {if($_.ProcessName.StartsWith("svchost")){return $_}}

但它仍然返回完整的输出。
参考:
How to break Foreach loop in Powershell?
Is it possible to terminate or stop a PowerShell pipeline from within a filter

编辑(关于大数据问题的说明):
示例(#2):
我有两个 XML:
A.xml:

<?xml version="1.0" encoding="UTF-8"?>
<Events>
  <Event>
    <EventData Name="Time">09/10/2017 12:54:16</EventData>
    <EventData Name="WorkstationName">USER2-PC</EventData>
    <EventData Name="UserName">user2</EventData>
  </Event>  
</Events>

B.xml:

<?xml version="1.0" encoding="UTF-8"?>
<Events>
   <Event>
    <EventData Name="Time">09/10/2017 14:54:16</EventData>
    <EventData Name="WorkstationName">USER1-PC</EventData>
    <EventData Name="UserName">user1</EventData>
  </Event>
  <Event>
    <EventData Name="Time">09/10/2017 13:54:16</EventData>
    <EventData Name="WorkstationName">USER2-PC</EventData>
    <EventData Name="UserName">user2</EventData>
  </Event> 
 ... (more 100,000 events like the above two)
</Events>

这些 XML 正在作为对象加载:

$fileA = "C:\tmp\A.xml"
$a = New-Object Xml.XmlDocument
$a.Load($fileA)

$fileB = "C:\tmp\B.xml"
$b = New-Object Xml.XmlDocument
$b.Load($fileB)

那我要搜索相同用户名的第一个匹配项:

$result = $b.Events.Event | Where-Object {
    (($_.EventData | where-object {$_.Name -eq "UserName"})."#text" -eq $username)
}

$result.EventData

在这种情况下,如果我在第一个事件上匹配,那么运行其余的 99,999 个事件是浪费时间。

编辑(已解决):
阅读尼克的回答后,我没有尝试任何新的东西。
命令:

Get-Process | Where-Object {if($_.ProcessName.StartsWith("svchost")){ $_;break;}}  

确实会停止Where-Object,但它不会返回该项目。
这个可以通过以下方式解决:

Get-Process | Where-Object {if($_.ProcessName.StartsWith("svchost")){ $someVar = $_;break;}}  

因此我标记了他的答案。

【问题讨论】:

  • ... | Where-Object { $_.ProcessName -like 'svchost*' } | Select-Object -First 1?
  • 如果您要过滤文件数据,为什么不使用带有 -List 选项的 Select-String 使其在第一次匹配时停止?
  • @AnsgarWiechers 它仍然会传递所有进程,在它使用 all 'svchost.exe' 进程获取对象后,它将选择第一个进程。您可以看到它通过:Get-Process | Where-Object { $_.ProcessName -like 'svchost*'; Write-Host $_} | Select-Object -First 1 传递所有对象

标签: powershell break pipeline


【解决方案1】:

Where-ObjectForEach-Object 都是 Cmdlet。您不能破坏 Cmdlet(命令)。你可以做的是像这样使用关键字foreach

$process = Get-Process

foreach ($item in $process) {
    if ($item.Name -eq 'svchost') {
        $item
        return
    }
}

【讨论】:

  • 我想你打算在这里使用break而不是return
  • @vrdse 这种解决方法的问题是您仍然传递所有对象。不仅如此,你还做了两次。第一次使用:$process = Get-Process,第二次使用循环:foreach ($item in $process)。在这种情况下,使用 Where-Object 会更快。
  • 我认为这很大程度上取决于何时在循环中找到第一项,不是吗?
【解决方案2】:

如果您需要效率,您可以尝试将其分解为一个循环:

Get-Process | foreach {If ($_.ProcessName.StartsWith("svchost")){$_;break}}

您可以通过此检查确认它是否有效:

$i=0; Get-Process | foreach {$i++;$i; If ($_.ProcessName.StartsWith("svchost")){$_;break}}

它会使循环在每次循环时打印出一个数字,在我的情况下它达到 115,然后如果我这样做 (Get-Process).Count 我有 157 进程,所以它循环遍历我的进程找到了我们想要的进程然后停止循环。

如其他答案中所述,您可以使用[0],在任何数组或列表上,您可以使用方括号内的索引选择单独的行,但要小心,因为在 null 或空对象上尝试此操作会抛出例外:

(Get-Process | Where-Object {$_.ProcessName.StartsWith("svchost")})[0]

或者你可以Select-Object,它以类似的方式工作,但有比索引更多的选项,如果对象为空或空,也不会抛出任何错误。

Get-Process | Where-Object {$_.ProcessName.StartsWith("svchost")} | Select-Object -First 1

在您选择第一个结果之前,这两个选项仍然会评估整个列表。

【讨论】:

  • 效率是我所需要的,这很重要。关于你提到的最后两个答案,就像你写的那样,他们仍然评估整个列表,因此我对他们不感兴趣。关于您的第一个建议,这很好,我也尝试过,但它没有返回请求项目。但是我可以通过保存请求的变量{$someVar = $_;break} 来解决这个问题。
  • 我看到这个作为一个独立的命令很好地工作,但它在脚本中不起作用 - 脚本在遇到 break 时退出。根据ss64.com/ps/foreach.html,看起来像管道到foreach 将其视为ForEach-Object 的别名,这可以解释为什么break 的行为就像它会在循环之外起作用。
【解决方案3】:

要从大文件中过滤数据,请使用 StreamReader 而不是常规的 PowerShell cmdlet:

$filename = 'C:\path\to\your.txt'
$word     = 'something'

$rdr = [IO.File]::OpenText($filename)
while ($rdr.Peek() -ge 0) {
    $line = $rdr.ReadLine()
    if ($line -like "*${word}*") { break }
}
$rdr.Close()
$rdr.Dispose()

【讨论】:

  • 我编辑了我的问题,我为我正在处理的大数据放置了示例。在这种数据中,我将数据加载为 XML 对象,因此在这种情况下我看不到如何使用 StreamReader。我最好的办法是使用管道Where-Object,但我无法在第一场比赛中停止它
  • 我可以使用foreach($event in $b.Events.Event){...},但我认为在管道中使用Foreach-Object可能会更快。
【解决方案4】:

超级有趣。我不知道为什么,但这篇文章与我们的发现相矛盾!

https://community.idera.com/database-tools/powershell/powertips/b/tips/posts/save-time-with-select-object-first

我什至测试过它。从 PS3 开始,select-object -first 停止流水线

【讨论】:

  • 正如目前所写,您的答案尚不清楚。请edit 添加其他详细信息,以帮助其他人了解这如何解决所提出的问题。你可以找到更多关于如何写好答案的信息in the help center
猜你喜欢
  • 1970-01-01
  • 2018-06-27
  • 2020-11-09
  • 2020-05-04
  • 2014-12-04
  • 2022-12-03
相关资源
最近更新 更多