使 Powershell 进度条更高效

Making an Powershell Progress Bar more efficient

我构建了一个脚本来从日志文件中过滤出多个消息。我现在使用的文件大约有 400.000 行,当我寻找与以下代码的匹配项时,他花了很长时间,因为我添加了进度条。有没有办法让它更有效率。如果我是对的,那么花这么长时间的原因是他每经过一行就刷新进度条 Gui。

$i= 0
$path = ""
$length = (Get-Content $path).Length

#Datum, Hostname und Message Nummer
$result = Get-Content $path | ForEach-Object {
    if($_ -match '(\d{2}\.\d{2}\.\d{4} \d{2}:\d{2}:\d{2}).*\(((?:\d{1,3}\.){3}\d{1,3})\) disconnected\.?\s+(\d+) message\[s\]'){
        try {
            $dns = [System.Net.Dns]::GetHostEntry($matches[2]).HostName
        }
        catch { 
            $dns = 'Not available' 
        }
        [PsCustomObject]@{
            IP       = $matches[2]
            Messages = [int]$matches[3]
            DNSName  = $dns
            Date     = [datetime]::ParseExact($matches[1], 'dd.MM.yyyy HH:mm:ss', $null)
        }
    }
     # update counter and write progress
   $i++
   Write-Progress -activity "Searching for matches" -status "Scanned: $i of $($length)" -percentComplete (($i / $length)  * 100)
 }

 #Messages Counted
 $cumulative = $result | Group-Object -Property IP | ForEach-Object {
    [PsCustomObject]@{
        IP = $_.Name
        Messages = ($_.Group | Measure-Object -Property Messages -Sum).Sum
        DNSName = $_.Group[0].DNSName
        Date    = ($_.Group | Sort-Object Date)[-1].Date
    }
}

在主机应用程序中更新进度条元素确实会在执行过程中占用时间和资源 - 但即使您抑制了进度条,写入进度流仍然很慢!

,解决方法是少调用Write-Progress

$result = Get-Content $path | ForEach-Object {
    # ...
    
    $i++
    if($i % 100 -eq 0){
        Write-Progress -activity "Searching for matches" -status "Scanned: $i of $($length)" -percentComplete (($i / $length)  * 100)
    }
}

这里我们只每 100 行写入一次进度流 - 减少 99% 的更新以阻碍执行速度:-)

详细说明我之前的命令:

Write-Progress is known to be slow, especially on Windows PowerShell. In other words, if you using Windows PowerShell, I recommend you to upgrade (or at least check) PowerShell Core.

为了加快速度,您可以考虑调用 Write-Progress 的次数与您的最大屏幕宽度一样多:

$Length = 400.000
(Measure-Command {
    $MaxScreenWidth = 200
    ForEach($i in (0..$Length)) {
        if ($i % [math]::floor($length / $MaxScreenWidth) -eq 0) { 
            Write-Progress -activity "Searching for matches" -status "Scanned: $i of $($length)" -percentComplete (($i / $length)  * 100)
        }
    }
}).TotalMilliSeconds

对于这个简单的示例,这大约是没有 if ($i % [math]::floor($length / $MaxScreenWidth) -eq 0) { 条件时(在 Windows PowerShell 上)的两倍,并且对于更多迭代或当您还输出到显示器时甚至更快同时.

备注:

  • 上面的例子假设一个连续的序列
  • 假定序列大于最大屏幕宽度
  • 在进行过程中不应更改(减少)屏幕宽度

为了避免上述缺点,您可能会选择更复杂的实现,例如:

$Length = 400.000
(Measure-Command {
    ForEach($i in (0..$Length)) {
        $Script:WindowWidthChanged = $Script:WindowWidth -ne $Host.UI.RawUI.WindowSize.Width
        if ($Script:WindowWidthChanged) { $Script:WindowWidth = $Host.UI.RawUI.WindowSize.Width }
        $ProgressCompleted = [math]::floor($i * $Script:WindowWidth / $length)
        if ($Script:WindowWidthChanged -or $ProgressCompleted -ne $Script:LastProgressCompleted) {
            Write-Progress -activity "Searching for matches" -status "Scanned: $i of $($length)" -percentComplete (($i / $length)  * 100)
        }
        $Script:LastProgressCompleted = $ProgressCompleted
    }
}).TotalMilliSeconds