Odczytaj plik wiersz po wierszu w programie PowerShell

103

Chcę czytać plik wiersz po wierszu w programie PowerShell. W szczególności chcę przejrzeć plik w pętli, zapisać każdy wiersz w zmiennej w pętli i wykonać pewne przetwarzanie w wierszu.

Znam odpowiednik Bash:

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

Niewiele dokumentacji na temat pętli programu PowerShell.

Kingamere
źródło
Wybrana odpowiedź Mateusza nie jest świetnym rozwiązaniem. Get-Contentładuje cały plik do pamięci naraz, co powoduje błąd lub zawieszanie się w przypadku dużych plików.
Kolob Canyon
1
@KolobCanyon, który jest całkowicie nieprawdziwy. Domyślnie Get-Content ładuje każdy wiersz jako jeden obiekt w potoku. Jeśli kierujesz do funkcji, która nie określa processbloku, i wypluwa inny obiekt w każdym wierszu do potoku, to ta funkcja jest problemem. Ewentualne problemy z wczytaniem pełnej zawartości do pamięci nie są spowodowane Get-Content.
The Fish
@TheFish Załaduje foreach($line in Get-Content .\file.txt)cały plik do pamięci przed rozpoczęciem iteracji. Jeśli mi nie wierzysz, weź plik dziennika 1 GB i wypróbuj.
Kanion Kolob,
2
@KolobCanyon To nie jest to, co powiedziałeś. Powiedziałeś, że Get-Content ładuje to wszystko do pamięci, co nie jest prawdą. Twój zmieniony przykład foreach byłby tak; foreach nie zna potoku. Get-Content .\file.txt | ForEach-Object -Process {}jest świadomy potoku i nie załaduje całego pliku do pamięci. Domyślnie Get-Content będzie przekazywać po jednym wierszu przez potok.
The Fish

Odpowiedzi:

180

Niewiele dokumentacji na temat pętli programu PowerShell.

Dokumentacja na pętli w PowerShell jest obfite, a może chcesz sprawdzić następujące tematy pomocy: about_For, about_ForEach, about_Do, about_While.

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

Innym idiomatycznym rozwiązaniem problemu w programie PowerShell jest przesyłanie wierszy z pliku tekstowego do polecenia ForEach-Objectcmdlet :

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

Zamiast dopasowywania wyrażeń regularnych wewnątrz pętli, możesz przepuścić linie, Where-Objectaby przefiltrować tylko te, które Cię interesują:

Get-Content .\file.txt | Where-Object {$_ -match $regex} | ForEach-Object {
    # Work here
}
Mathias R. Jessen
źródło
Linki nie są uszkodzone, ale teraz przekierowują do docs.microsoft.com.
Peter Mortensen
@KolobCanyon, o którym nigdy nie wspomniano jako problem w OP.
The Fish
53

Get-Contentma złą wydajność; próbuje od razu wczytać plik do pamięci.

Czytnik plików C # (.NET) czyta każdy wiersz po kolei

Najlepsza wydajność

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

Lub nieco mniej wydajne

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

foreachOświadczenie będzie prawdopodobnie nieco szybciej niż ForEach-Object(patrz komentarze poniżej, aby uzyskać więcej informacji).

Kolob Canyon
źródło
5
Prawdopodobnie użyłbym [System.IO.File]::ReadLines("C:\path\to\file.txt") | ForEach-Object { ... }. foreachOświadczenie będzie załadować całą kolekcję do obiektu . ForEach-Objectużywa potoku do przesyłania strumieniowego. Teraz foreachinstrukcja prawdopodobnie będzie nieco szybsza niż ForEach-Objectpolecenie, ale to dlatego, że ładowanie całości do pamięci zwykle jest szybsze. Get-Contentjest jednak nadal straszna.
Bacon Bits
@BaconBits foreach()jest aliasemForeach-Object
Kolob Canyon,
16
To bardzo powszechne nieporozumienie. foreachjest stwierdzenie, jak if, forlub while. ForEach-Objectto polecenie, jak Get-ChildItem. Istnieje również domyślny alias foreachfor ForEach-Object, ale jest on używany tylko wtedy, gdy istnieje potok. Zobacz długie wyjaśnienie w Get-Help about_Foreachlub kliknij łącze w moim poprzednim komentarzu, który prowadzi do całego artykułu The Scripting Guys firmy Microsoft na temat różnic między instrukcją a poleceniem.
Bacon Bits
4
@BaconBits blogs.technet.microsoft.com/heyscriptingguy/2014/07/08/ ... Dowiedziałem się czegoś nowego. Dzięki. Założyłem, że są takie same, ponieważ Get-Alias foreach=> Foreach-Object, ale masz rację, są różnice
Kolob Canyon
2
To zadziała, ale będziesz chciał zmienić $linena $_w bloku skryptu pętli.
Bacon Bits
1

Wszechmocny przełącznik działa tutaj dobrze:

'one
two
three' > file

$regex = '^t'

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

Wynik:

line is two
line is three
js2010
źródło