2010-11-16 18 views
79

Pracuję z kilkoma wielobajtowymi plikami tekstowymi i chcę wykonać na nich przetwarzanie strumienia za pomocą PowerShell. To proste rzeczy, po prostu analizowanie każdej linii i wyciąganie niektórych danych, a następnie zapisywanie ich w bazie danych.Jak przetwarzać plik w PowerShell wiersz po wierszu jako strumień

Niestety, get-content | %{ whatever($_) } wydaje się przechowywać cały zestaw linii na tym etapie rury w pamięci. Jest to także zaskakująco wolno, robiąc bardzo dużo czasu, aby rzeczywiście przeczytać wszystko w

Więc moje pytanie jest na dwie części:.

  1. W jaki sposób można uczynić go przetworzyć linii strumienia przez linię i nie trzymać całej karty rzecz buforowana w pamięci? W tym celu chciałbym uniknąć wykorzystania kilku pamięci RAM.
  2. Jak sprawić, by działał szybciej? PowerShell iterujący po numerze get-content wydaje się być 100 razy wolniejszy niż skrypt C#.

Mam nadzieję, że coś jest głupie tu robię, jak brakuje parametru -LineBufferSize czy coś ...

+8

Aby przyspieszyć 'get-content' up, ustaw -ReadCount na 512. Zauważ, że w tym momencie $ _ w Foreach będzie tablicą ciągów. –

+1

Mimo to, skorzystałbym z sugestii Romana użycia czytnika .NET - znacznie szybciej. –

+0

Z ciekawości, co się dzieje, gdy nie zależy mi na prędkości, ale na pamięci? Najprawdopodobniej pójdę z sugestią czytelnika .NET, ale jestem też zainteresowany tym, jak powstrzymać go od buforowania całej rury w pamięci. – scobi

Odpowiedz

79

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

UPDATE: Jeśli nadal nie boją następnie 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

Istnieje komentarze o możliwie lepszego/krótszego kodu. Nie ma nic złego w oryginalnym kodzie z for i nie jest to pseudo-kod. Ale krótszy (najkrótsza?) Wariant pętli czytania jest

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

Co jest nie tak z oryginalną częścią? To tylko fakt. – stej

+3

FYI, kompilacja skryptów w PowerShell V3 nieco poprawia sytuację. Pętla "prawdziwa praca" przeszła z 117 sekund na V2 do 62 sekund na V3 wpisanej na konsoli. Kiedy umieszczam pętlę w skrypcie i mierzy wykonanie skryptu na V3, spada do 34 sekund. –

+0

Umieściłem wszystkie trzy testy w skrypcie i otrzymałem następujące wyniki: V3 Beta: 20/27/83 sekund; V2: 14/21/101. Wygląda na to, że w moim eksperymencie V3 jest szybszy w teście 3, ale w pierwszych dwóch jest wolniejszy. Cóż, to wersja beta, mam nadzieję, że wydajność poprawi się w RTM. –

47

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

Wymaga .NET 4.0 lub wyższej.

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

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

+6

Notatka jest potrzebna: .NET Framework - Obsługiwane w: 4.5, 4. Tak więc, może nie działać w V2 lub V1 na niektórych komputerach. –

+0

To dało mi System.IO.File nie istnieje błąd, ale kod powyżej Roman pracował dla mnie –

9

Jeśli chcesz użyć prosto PowerShell sprawdzić poniższy kod.

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

To, co OP chciał się pozbyć, ponieważ 'Get-Content' jest bardzo wolny na dużych plików. –

Powiązane problemy