Lesen Sie die Datei Zeile für Zeile in PowerShell

100

Ich möchte eine Datei Zeile für Zeile in PowerShell lesen. Insbesondere möchte ich die Datei durchlaufen, jede Zeile in einer Variablen in der Schleife speichern und einige Verarbeitungen in der Zeile durchführen.

Ich kenne das Bash-Äquivalent:

while read line do
    if [[ $line =~ $regex ]]; then
          # work here
    fi
done < file.txt

Nicht viel Dokumentation zu PowerShell-Schleifen.

Kingamere
quelle
Die ausgewählte Antwort von Mathias ist keine gute Lösung. Get-ContentLädt die gesamte Datei auf einmal in den Speicher, was bei großen Dateien fehlschlägt oder einfriert.
Kolob Canyon
@ KolobCanyon das ist völlig falsch. Standardmäßig lädt Get-Content jede Zeile als ein Objekt in die Pipeline. Wenn Sie zu einer Funktion weiterleiten, die keinen processBlock angibt und pro Zeile ein anderes Objekt in die Pipeline ausspuckt, ist diese Funktion das Problem. Probleme beim Laden des gesamten Inhalts in den Speicher sind nicht die Schuld von Get-Content.
Der Fisch
@TheFish foreach($line in Get-Content .\file.txt)Es wird die gesamte Datei in den Speicher geladen, bevor die Iteration beginnt. Wenn Sie mir nicht glauben, holen Sie sich eine 1-GB-Protokolldatei und probieren Sie es aus.
Kolob Canyon
1
@ KolobCanyon Das hast du nicht gesagt. Sie sagten, dass Get-Content alles in den Speicher lädt, was nicht stimmt. Ihr geändertes Beispiel für foreach würde ja; foreach ist nicht Pipeline-fähig. Get-Content .\file.txt | ForEach-Object -Process {}ist Pipeline-fähig und lädt nicht die gesamte Datei in den Speicher. Standardmäßig durchläuft Get-Content jeweils eine Zeile durch die Pipeline.
Der Fisch

Antworten:

176

Nicht viel Dokumentation zu PowerShell-Schleifen.

Dokumentation über Schleifen in Powershell ist reichlich vorhanden, und Sie können die folgenden Hilfethemen prüfen wollen: about_For, about_ForEach, about_Do, about_While.

foreach($line in Get-Content .\file.txt) {
    if($line -match $regex){
        # Work here
    }
}

Eine andere idiomatische PowerShell-Lösung für Ihr Problem besteht darin, die Zeilen der Textdatei an das ForEach-ObjectCmdlet weiterzuleiten :

Get-Content .\file.txt | ForEach-Object {
    if($_ -match $regex){
        # Work here
    }
}

Anstatt den regulären Ausdruck innerhalb der Schleife abzugleichen, können Sie die Zeilen durchleiten Where-Object, um nur die zu filtern, an denen Sie interessiert sind:

Get-Content .\file.txt | Where-Object {$_ -match $regex} | ForEach-Object {
    # Work here
}
Mathias R. Jessen
quelle
Die Links sind nicht defekt, aber sie leiten jetzt weiter docs.microsoft.com.
Peter Mortensen
@KolobCanyon, der im OP nie als Problem erwähnt wurde.
Der Fisch
51

Get-Contenthat schlechte Leistung; Es wird versucht, die Datei auf einmal in den Speicher einzulesen.

Der C # (.NET) -Dateireader liest jede Zeile einzeln

Beste Leistung

foreach($line in [System.IO.File]::ReadLines("C:\path\to\file.txt"))
{
       $line
}

Oder etwas weniger performant

[System.IO.File]::ReadLines("C:\path\to\file.txt") | ForEach-Object {
       $_
}

Die foreachAussage wird wahrscheinlich etwas schneller sein als ForEach-Object(siehe Kommentare unten für weitere Informationen).

Kolob Canyon
quelle
5
Ich würde wahrscheinlich verwenden [System.IO.File]::ReadLines("C:\path\to\file.txt") | ForEach-Object { ... }. Die foreachAnweisung lädt die gesamte Sammlung in ein Objekt . ForEach-Objectverwendet eine Pipeline zum Streamen. Jetzt ist die foreachAnweisung wahrscheinlich etwas schneller als der ForEach-ObjectBefehl, aber das liegt daran, dass das Laden des Ganzen in den Speicher normalerweise schneller ist. Get-Contentist jedoch immer noch schrecklich.
Bacon Bits
@ BaconBits foreach()ist ein Alias ​​vonForeach-Object
Kolob Canyon
15
Das ist ein sehr häufiges Missverständnis. foreachist eine Aussage, wie if, foroder while. ForEach-Objectist ein Befehl, wie Get-ChildItem. Es gibt auch einen Standardalias von foreachfor ForEach-Object, der jedoch nur verwendet wird, wenn eine Pipeline vorhanden ist. Lesen Sie die lange Erklärung in Get-Help about_Foreachoder klicken Sie auf den Link in meinem vorherigen Kommentar, der zu einem ganzen Artikel von Microsoft's The Scripting Guys über die Unterschiede zwischen der Anweisung und dem Befehl führt.
Bacon Bits
3
@BaconBits blogs.technet.microsoft.com/heyscriptingguy/2014/07/08/… Ich habe etwas Neues gelernt. Vielen Dank. Ich nahm an, dass sie gleich waren, weil Get-Alias foreach=> Foreach-Object, aber Sie haben Recht, es gibt Unterschiede
Kolob Canyon
2
Das wird funktionieren, aber Sie ändern mögen , $lineum $_in der Script - Block-Schleife.
Bacon Bits
1

Der allmächtige Schalter funktioniert hier gut:

'one
two
three' > file

$regex = '^t'

switch -regex -file file { 
  $regex { "line is $_" } 
}

Ausgabe:

line is two
line is three
js2010
quelle