2015-09-03 10 views
5

Ich habe Standard-Apache-Protokolldateien, zwischen 500 MB und 2 GB groß. Ich brauche die Linien in sie zu sortieren (jede Zeile beginnt mit einem Datum yyyy-MM-dd hh: mm: ss, so dass keine Behandlung notwendig für die SortierungSortieren sehr große Textdatei in PowerShell

Die einfachste und naheliegendste Sache, die den Sinn kommt, ist

.
Get-Content unsorted.txt | sort | get-unique > sorted.txt 

ich raten bin (ohne es versucht zu haben), dass dies zu tun Get-Content mit immer in meinen 1 GB Dateien nehmen würde. ich weiß um nicht ganz meine Art und Weise System.IO.StreamReader, aber ich bin neugierig, ob eine effiziente Lösung genommen werden kann zusammen mit das?

Dank an alle, die eine effizientere Idee haben könnten.

[bearbeiten]

Ich versuchte dies anschließend, und es dauerte eine sehr lange Zeit; ungefähr 10 Minuten für 400 MB.

+0

habe ich versucht, den Befehl über und es dauerte in der Tat eine lange Zeit (ca. 10 Minuten auf 460 MB), und das Endergebnis war nicht das, was ich brauchte, und die Zieldatei ('sorted.txt') beendete die Größe zweimal nach oben von der Quelle. –

+0

Die Größenunterschiede sind wahrscheinlich auf unterschiedliche Codierungen zurückzuführen. Ersetzen Sie die '> sorted.txt' durch etwas wie' | Set-Content sorted.txt' könnte den Trick machen, sonst könnten Sie '| versuchen Out-File sorted.txt -Encoding '. – notjustme

+0

Mit Ihrem Vorschlag '| Set-Content sort.txt' half dabei, es richtig zu sortieren, aber es ist immer noch ziemlich langsam. Das Hinzufügen von '-ReadCount 5000' nach' Get-Content' macht zwar viel schneller, aber die Sortierung ist kaputt. Ich schätze, um richtig sortieren zu können, müssen wir Zeile für Zeile und nicht Block für Block lesen ... Ich wünschte, es gäbe einen effizienteren Weg. –

Antwort

5

Get-Content zum Lesen großer Dateien furchtbar ineffektiv ist. Sort-Object ist auch nicht sehr schnell.

Lasst uns eine Basislinie einzurichten:

$sw = [System.Diagnostics.Stopwatch]::StartNew(); 
$c = Get-Content .\log3.txt -Encoding Ascii 
$sw.Stop(); 
Write-Output ("Reading took {0}" -f $sw.Elapsed); 

$sw = [System.Diagnostics.Stopwatch]::StartNew(); 
$s = $c | Sort-Object; 
$sw.Stop(); 
Write-Output ("Sorting took {0}" -f $sw.Elapsed); 

$sw = [System.Diagnostics.Stopwatch]::StartNew(); 
$u = $s | Get-Unique 
$sw.Stop(); 
Write-Output ("uniq took {0}" -f $sw.Elapsed); 

$sw = [System.Diagnostics.Stopwatch]::StartNew(); 
$u | Out-File 'result.txt' -Encoding ascii 
$sw.Stop(); 
Write-Output ("saving took {0}" -f $sw.Elapsed); 

Mit einer 40 MB-Datei 1600000 Leitungen (hergestellt von 100k einzigartigen Linien wiederholt 16mal) dieses Skript die folgende Ausgabe auf meiner Maschine produziert:

Reading took 00:02:16.5768663 
Sorting took 00:02:04.0416976 
uniq took 00:01:41.4630661 
saving took 00:00:37.1630663 

Völlig unscheinbar: mehr als 6 Minuten winzige Datei zu sortieren. Jeder Schritt kann sehr verbessert werden. Lassen Sie sich StreamReader verwenden, um Datei Zeile für Zeile in HashSet zu lesen, welche Duplikate entfernen, kopieren Sie Daten zu List und es dort sortieren, dann StreamWriter verwenden, um Ergebnisse zu entleeren zurück.

$hs = new-object System.Collections.Generic.HashSet[string] 
$sw = [System.Diagnostics.Stopwatch]::StartNew(); 
$reader = [System.IO.File]::OpenText("D:\log3.txt") 
try { 
    while (($line = $reader.ReadLine()) -ne $null) 
    { 
     $t = $hs.Add($line) 
    } 
} 
finally { 
    $reader.Close() 
} 
$sw.Stop(); 
Write-Output ("read-uniq took {0}" -f $sw.Elapsed); 

$sw = [System.Diagnostics.Stopwatch]::StartNew(); 
$ls = new-object system.collections.generic.List[string] $hs; 
$ls.Sort(); 
$sw.Stop(); 
Write-Output ("sorting took {0}" -f $sw.Elapsed); 

$sw = [System.Diagnostics.Stopwatch]::StartNew(); 
try 
{ 
    $f = New-Object System.IO.StreamWriter "d:\result2.txt"; 
    foreach ($s in $ls) 
    { 
     $f.WriteLine($s); 
    } 
} 
finally 
{ 
    $f.Close(); 
} 
$sw.Stop(); 
Write-Output ("saving took {0}" -f $sw.Elapsed); 

dieses Skript erzeugt:

read-uniq took 00:00:32.2225181 
sorting took 00:00:00.2378838 
saving took 00:00:01.0724802 

Auf der gleichen Eingabedatei es mehr als 10-mal schneller läuft. Ich bin immer noch überrascht, obwohl es 30 Sekunden dauert, um die Datei von der Festplatte zu lesen.

+0

Geben Sie Measure-Command einen Versuch: https://technet.microsoft.com/en-us/library/Hh849910.aspx?f=255&MSPPError=-2147217396 –

+0

Dies ist eine deutliche Leistungsverbesserung, jedoch ist die Zieldatei deutlich kleiner als die Quelle. Doppelte Einträge scheinen gelöscht, was ich nicht möchte. Alles was ich tun muss, ist die Zeilen alphabetisch zu sortieren; Wenn es mehrere identische Linien gibt, behalte sie alle. Danke für die Hilfe! –

+0

Ihr Beispielcode namens "Get-Unique" entfernt Duplikate. Wenn Sie es nicht brauchen, dann lesen Sie einfach direkt zu 'List' und sortieren, ohne' HashSet' zu verwenden. – n0rd

0

(Herausgegeben auf n0rd Kommentare basiert mehr klar zu sein)

Es ist möglicherweise ein Speicherproblem sein. Da Sie die gesamte Datei in den Speicher laden, um sie zu sortieren (und den Overhead der Pipe in Sort-Object und die Pipe in Get-Unique zu addieren), ist es möglich, dass Sie die Speichergrenzen der Maschine erreichen und erzwingen um auf die Festplatte zu blättern, was die Dinge sehr verlangsamen wird. Eine Sache, die Sie in Betracht ziehen könnten, besteht darin, die Protokolle aufzuteilen, bevor sie sortiert werden, und sie dann wieder zusammenzufügen.

Das passt wahrscheinlich nicht genau zu Ihrem Format, aber wenn ich eine große Protokolldatei für, sagen wir, 16.08.2012 habe, die mehrere Stunden umfasst, kann ich sie für jede Stunde in eine andere Datei aufteilen mit so etwas wie folgt aus:

for($i=0; $i -le 23; $i++){ Get-Content .\u_ex120816.log | ? { $_ -match "^2012-08-16 $i`:" } | Set-Content -Path "$i.log" } 

Dies ist ein regulärer Ausdruck für jede Stunde des Tages zu schaffen und alle die passenden Log-Einträge in eine kleinere Protokolldatei von der Stunde genannt Dumping (zB 16.log, 17.log) . sie

for($i=0; $i -le 23; $i++){ Get-Content "$i.log" | sort | get-unique > "$isorted.txt" } 

Und dann können Sie splice wieder zusammen:

Dann wird Ihr Prozess des Sortierens und immer eindeutige Einträge in einem viel kleineren Teilmengen, die viel laufen soll schneller laufen kann ich.

Je nach der Häufigkeit der Protokolle kann es sinnvoller sein, sie nach Tag oder Minute aufzuteilen; die Hauptsache ist, sie in überschaubare Brocken zum Sortieren zu bringen.

Auch dies macht nur Sinn, wenn Sie die Speichergrenzen des Computers treffen (oder wenn Sort-Object einen wirklich ineffizienten Algorithmus verwendet).

+0

Sortieren eines großen Chunks ist nicht langsamer als mehrere kleinere Chunks, vorausgesetzt, alle Daten passen in den Speicher (dh nichts verschüttet zu tauschen) – n0rd

+0

@ n0rd - es hängt von der Größe der Datei ab, wie viel Speicher die Maschine zur Verfügung hat, den Algorithmus Sort-Object verwendet und wie nahe die Daten vorher sortiert sind. –

+0

Bei gleichen Eingabedaten wäre die Sortierung eines ganzen Satzes niemals langsamer als das Sortieren von Teilen mit demselben Algorithmus und dann das Zusammenführen. Für externe Sortierung (wenn alle Daten nicht in den Speicher passen), ja, müssen Sie teilen, sortieren und zusammenführen. Sonst gibt es keinen Gewinn dafür. – n0rd

0

Wenn jede Zeile des Protokolls mit einem Zeitstempel vorangestellt wird, und die Protokollmeldungen enthalten keine eingebetteten Zeilenumbrüche (die eine besondere Behandlung erfordern würde), ich denke, es weniger Speicher und Ausführungszeit dauern würde, den Zeitstempel von [String] zu konvertieren zu [DateTime] vor dem Sortieren. Im Folgenden wird angenommen jeder Protokolleintrag des Formats ist yyyy-MM-dd HH:mm:ss: <Message> (beachten Sie, dass die HH format specifier für eine 24-Stunden-Uhr verwendet wird):

Get-Content unsorted.txt 
    | ForEach-Object { 
     # Ignore empty lines; can substitute with [String]::IsNullOrWhitespace($_) on PowerShell 3.0 and above 
     if (-not [String]::IsNullOrEmpty($_)) 
     { 
      # Split into at most two fields, even if the message itself contains ': ' 
      [String[]] $fields = $_ -split ': ', 2; 

      return New-Object -TypeName 'PSObject' -Property @{ 
       Timestamp = [DateTime] $fields[0]; 
       Message = $fields[1]; 
      }; 
     } 
    } | Sort-Object -Property 'Timestamp', 'Message'; 

Wenn Sie die Verarbeitung der Eingabedatei für interaktive Anzeigezwecken Sie können über die Pipeline über die in Out-GridView oder Format-Table, um die Ergebnisse anzuzeigen.Wenn Sie die sortierten Ergebnisse speichern Sie können über die Pipeline die oben in die folgenden:

| ForEach-Object { 
     # Reconstruct the log entry format of the input file 
     return '{0:yyyy-MM-dd HH:mm:ss}: {1}' -f $_.Timestamp, $_.Message; 
    } ` 
    | Out-File -Encoding 'UTF8' -FilePath 'sorted.txt';