Pracuję z kilkoma wielogigabajtowymi plikami tekstowymi i chcę wykonać na nich przetwarzanie strumieniowe za pomocą PowerShell. To proste, wystarczy przeanalizować każdą linię i wyciągnąć trochę danych, a następnie zapisać je w bazie danych.
Niestety, get-content | %{ whatever($_) }
wydaje się, że cały zestaw linii na tym etapie rury jest w pamięci. Jest również zaskakująco wolny, a jego przeczytanie zajmuje bardzo dużo czasu.
Więc moje pytanie składa się z dwóch części:
- Jak sprawić, by przetwarzał strumień linia po linii i nie przechowywał całej rzeczy w pamięci? Chciałbym uniknąć zużywania w tym celu kilku gigabajtów pamięci RAM.
- Jak mogę sprawić, by działał szybciej? Iteracja programu PowerShell po a
get-content
wydaje się być 100 razy wolniejsza niż skrypt C #.
Mam nadzieję, że robię tu coś głupiego, na przykład brak -LineBufferSize
parametru lub coś ...
powershell
stream
scobi
źródło
źródło
get-content
, ustaw -ReadCount na 512. Zauważ, że w tym momencie $ _ w Foreach będzie tablicą ciągów.Get-Content
do zmiennej, ponieważ spowoduje to załadowanie całego pliku do pamięci. Domyślnie w piplelineGet-Content
przetwarza plik po jednej linii na raz. Dopóki nie gromadzisz wyników ani nie używasz polecenia cmdlet, które gromadzi się wewnętrznie (np. Sort-Object i Group-Object), trafienie w pamięć nie powinno być takie złe. Foreach-Object (%) to bezpieczny sposób przetwarzania każdej linii, pojedynczo.get-content | % -End { }
, narzeka, ponieważ nie podałeś bloku procesu. Więc nie może domyślnie używać -End, musi domyślnie używać -Process. I spróbuj1..5 | % -process { } -end { 'q' }
zobaczyć, że blok końcowy zdarza się tylko raz, zwyklegc | % { $_ }
nie działałby, gdyby blok skryptu domyślnie był -End ...Odpowiedzi:
Jeśli naprawdę zamierzasz pracować na wielogigabajtowych plikach tekstowych, nie używaj programu PowerShell. Nawet jeśli znajdziesz sposób, aby go przeczytać, szybsze przetwarzanie ogromnej liczby linii i tak będzie wolne w PowerShell i nie możesz tego uniknąć. Nawet proste pętle są drogie, powiedzmy na 10 milionów iteracji (całkiem realne w twoim przypadku) mamy:
# "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' } }
AKTUALIZACJA: Jeśli nadal się nie boisz, spróbuj użyć czytnika .NET:
$reader = [System.IO.File]::OpenText("my.log") try { for() { $line = $reader.ReadLine() if ($line -eq $null) { break } # process the line $line } } finally { $reader.Close() }
AKTUALIZACJA 2
Pojawiają się komentarze na temat prawdopodobnie lepszego / krótszego kodu. Nie ma nic złego w oryginalnym kodzie z
for
i nie jest to pseudokod. Ale krótszy (najkrótszy?) Wariant pętli czytania jest$reader = [System.IO.File]::OpenText("my.log") while($null -ne ($line = $reader.ReadLine())) { $line }
źródło
do { $line = $reader.ReadLine(); $line } while ($line -neq $null)
for ( $line = $reader.ReadLine(); $line -ne $null; $line = $reader.ReadLine() ) { $line }
while($null -ne ($line = $read.ReadLine())) {$line}
. Ale temat tak naprawdę nie dotyczy takich rzeczy.System.IO.File.ReadLines()
jest idealny do tego scenariusza. Zwraca wszystkie wiersze pliku, ale pozwala natychmiast rozpocząć iterację po wierszach, co oznacza, że nie musi przechowywać całej zawartości w pamięci.Wymaga platformy .NET 4.0 lub nowszej.
foreach ($line in [System.IO.File]::ReadLines($filename)) { # do something with $line }
http://msdn.microsoft.com/en-us/library/dd383503.aspx
źródło
Jeśli chcesz korzystać z prostego programu PowerShell, zapoznaj się z poniższym kodem.
$content = Get-Content C:\Users\You\Documents\test.txt foreach ($line in $content) { Write-Host $line }
źródło
Get-Content
działa bardzo wolno w przypadku dużych plików.