Ich muss oft große Logs auslesen und diese anschließend weiterverarbeiten. In den Logs kommen viele Einträge mehrfach vor, die für mich uninteressant sind und rausfiltern muss.
Problem
Üblicherweise verwende ich dafür den Get-Content | Sort -Unique Befehl.
Kleine Dokumente lassen sich damit wunderbar und unkompliziert verarbeiten. Bei Logs mit mehr als 100.000 Einträgen kann der Prozess mehrere Stunden dauern und den Arbeitsspeicher stark auslasten. Da es mit Notepad++ oder sogar in Excel schneller funktioniert musste das auch in Powershell möglich sein.
Lösung
Die Lösung fand ich auf stackoverflow.com und heißt HashSet.
A set is a collection that contains no duplicate elements, and whose elements are in no particular order.
https://docs.microsoft.com/en-us/dotnet/api/system.collections.generic.hashset-1?view=netframework-4.8#remarks
Funktion
Mit den Informationen habe ich die Get-FastUniqSort Funktion für Powershell geschrieben. Damit lässt sich der Inhalt eines Text Dokuments sehr schnell sortieren und doppelte Einträge entfernen. Die Original-Dokumente werden dabei überschrieben.
FUNCTION Get-FastUniqSort{
PARAM(
[Parameter(
Mandatory=$true,
ValueFromPipeline=$true,
ValueFromPipelineByPropertyName=$true,
HelpMessage="Path")]
[ValidateScript({Test-Path $_ })]
[Alias('FullName')]
[String[]]$FilePath
)
PROCESS{
FOREACH($path in $FilePath){
# input
Write-Output "$path"
# read
$HashSet = new-object System.Collections.Generic.HashSet[string]
$StopWatch = [System.Diagnostics.Stopwatch]::StartNew();
$i = 0
$read = [System.IO.File]::OpenText($FilePath)
while (($row = $read.ReadLine()) -ne $null){
$t = $HashSet.Add($row)
$i++
}
$read.Close()
$FilePathRows = $i
$StopWatch.Stop();
Write-Verbose $("duration to read $FilePathRows unique rows: {0}" -f $StopWatch.Elapsed)
# sort
$StopWatch = [System.Diagnostics.Stopwatch]::StartNew();
$sort = new-object system.collections.generic.List[string] $HashSet;
$sort.Sort();
$StopWatch.Stop();
Write-Verbose $("duration to sort: {0}" -f $StopWatch.Elapsed);
# write
$StopWatch = [System.Diagnostics.Stopwatch]::StartNew();
$write = New-Object System.IO.StreamWriter $path;
foreach ($row in $sort){
$write.WriteLine($row);
}
$write.Close();
$StopWatch.Stop();
Write-Verbose $("duration to write: {0}" -f $StopWatch.Elapsed);
}
}
}
Beispiel
Ein Dokument mit mehr als 2.000.000 Einträgen lässt sich damit in unter 4 Sekunden sortieren. PS C:\Users\Johannes> Get-FastUniqSort "C:\Logs\big.log" -Verbose
C:\Logs\big.log
AUSFÜHRLICH: duration to read 2558946 unique rows: 00:00:01.5751712
AUSFÜHRLICH: duration to sort: 00:00:01.8776333
AUSFÜHRLICH: duration to write: 00:00:00.1894716