Jak przetwarzać plik w PowerShell linia po linii jako strumień

Pracuję z kilkoma wielogigabajtowymi plikami tekstowymi i chcę je przetworzyć za pomocą PowerShell. To proste, po prostu parsowanie każdej linii i wyciąganie niektórych danych, a następnie przechowywanie ich w bazie danych.

Niestety, get-content | %{ whatever($_) } wydaje się zachować cały zestaw linii na tym etapie rury w pamięci. Jest również zaskakująco powolny, zajmuje bardzo dużo czasu, aby przeczytać to wszystko.

Więc moje pytanie składa się z dwóch części:

  1. Jak mogę to przetworzyć streamuj linię po linii i nie zachowuj całej rzeczy buforowanej w pamięci? Chciałbym uniknąć zużywania w tym celu kilku koncertów pamięci RAM.
  2. Jak sprawić, by biegła szybciej? Iteracja PowerShell ' a nad get-content wydaje się być 100x wolniejsza niż skrypt C#.

Mam nadzieję, że robię tu coś głupiego, jak Brak parametru -LineBufferSize czy coś...

Author: Peter Mortensen, 2010-11-16

3 answers

Jeśli naprawdę zamierzasz pracować na wielogigabajtowych plikach tekstowych, nie używaj PowerShell. Nawet jeśli znajdziesz sposób, aby go przeczytać szybsze przetwarzanie ogromnej ilości linii będzie powolne w PowerShell i tak i nie można tego uniknąć. Nawet proste pętle są drogie, powiedzmy za 10 milionów iteracji (całkiem realnych 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()
}

UPDATE 2

Są komentarze o prawdopodobnie lepszym / krótszym kodzie. Nie ma nic złego w oryginalnym kodzie z for i nie jest to pseudo-kod. Ale krótszy (najkrótszy?) wariantem pętli odczytu jest

$reader = [System.IO.File]::OpenText("my.log")
while($null -ne ($line = $reader.ReadLine())) {
    $line
}
 83
Author: Roman Kuzmin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-12-06 15:13:13

System.IO.File.ReadLines() jest idealny do tego scenariusza. Zwraca wszystkie linie pliku, ale pozwala natychmiast rozpocząć iterację nad liniami, co oznacza, że nie musi przechowywać całej zawartości w pamięci.

Wymaga. NET 4.0 lub wyższej wersji.

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

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

 48
Author: Despertar,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-12-03 02:13:45

Jeśli chcesz użyć straight PowerShell sprawdź poniższy kod.

$content = Get-Content C:\Users\You\Documents\test.txt
foreach ($line in $content)
{
    Write-Host $line
}
 7
Author: Chris Blydenstein,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-07-07 21:51:11