99 Stimmen

So verarbeiten Sie eine Datei in PowerShell zeilenweise als Stream

Ich arbeite mit einigen Multi-Gigabyte-Textdateien und möchte sie mit PowerShell als Stream verarbeiten. Es ist einfaches Zeug, nur Parsing jede Zeile und ziehen Sie einige Daten, dann speichern Sie es in einer Datenbank.

Leider, get-content | %{ whatever($_) } scheint den gesamten Satz von Zeilen in diesem Stadium der Pipe im Speicher zu halten. Es ist auch erstaunlich langsam, da es sehr lange dauert, bis alles eingelesen ist.

Meine Frage besteht also aus zwei Teilen:

  1. Wie kann ich erreichen, dass der Stream Zeile für Zeile verarbeitet wird und nicht alles im Speicher gepuffert bleibt? Ich möchte vermeiden, dass mehrere Gigabyte RAM für diesen Zweck verbraucht werden.
  2. Wie kann ich es schneller laufen lassen? PowerShell iteriert über eine get-content scheint 100x langsamer zu sein als ein C#-Skript.

Ich hoffe, dass ich irgendetwas Dummes tue, zum Beispiel ein -LineBufferSize Parameter oder so etwas...

97voto

Roman Kuzmin Punkte 38429

Wenn Sie wirklich an Textdateien mit einer Größe von mehreren Gigabyte arbeiten wollen, sollten Sie PowerShell nicht verwenden. Selbst wenn Sie einen Weg finden, sie schneller zu lesen, wird die Verarbeitung großer Mengen von Zeilen in PowerShell ohnehin langsam sein, und das lässt sich nicht vermeiden. Selbst einfache Schleifen sind teuer, sagen wir für 10 Millionen Iterationen (in Ihrem Fall ziemlich real):

# "empty" loop: takes 10 seconds
measure-command { for($i=0; $i -lt 10000000; ++$i) {} }

# "simple" job, just output: takes 20 seconds
measure-command { for($i=0; $i -lt 10000000; ++$i) { $i } }

# "more real job": 107 seconds
measure-command { for($i=0; $i -lt 10000000; ++$i) { $i.ToString() -match '1' } }

UPDATE: Wenn Sie sich immer noch nicht trauen, versuchen Sie es mit dem .NET-Reader:

$reader = [System.IO.File]::OpenText("my.log")
try {
    for() {
        $line = $reader.ReadLine()
        if ($line -eq $null) { break }
        # process the line
        $line
    }
}
finally {
    $reader.Close()
}

UPDATE 2

Es gibt Kommentare über möglicherweise besseren/kürzeren Code. Es ist nichts falsch mit dem ursprünglichen Code mit for und es ist kein Pseudocode. Aber die kürzere (kürzeste?) Variante der Leseschleife ist

$reader = [System.IO.File]::OpenText("my.log")
while($null -ne ($line = $reader.ReadLine())) {
    $line
}

53voto

Despertar Punkte 20373

System.IO.File.ReadLines() ist perfekt für dieses Szenario. Sie gibt alle Zeilen einer Datei zurück, lässt Sie aber sofort mit der Iteration über die Zeilen beginnen, was bedeutet, dass nicht der gesamte Inhalt im Speicher abgelegt werden muss.

Erfordert .NET 4.0 oder höher.

foreach ($line in [System.IO.File]::ReadLines($filename)) {
    # do something with $line
}

http://msdn.microsoft.com/en-us/library/dd383503.aspx

2voto

Chris Blydenstein Punkte 237

Wenn Sie direkt PowerShell verwenden möchten, sehen Sie sich den folgenden Code an.

$content = Get-Content C:\Users\You\Documents\test.txt
foreach ($line in $content)
{
    Write-Host $line
}

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X