2010-11-16 16 views
79

Sto lavorando con alcuni file di testo multi-gigabyte e voglio eseguire l'elaborazione in streaming su di essi utilizzando PowerShell. È roba semplice, basta analizzare ogni riga e tirare fuori alcuni dati, quindi archiviarli in un database.Come elaborare un file in PowerShell line-by-line come stream

Sfortunatamente, get-content | %{ whatever($_) } sembra mantenere l'intero set di linee in questa fase della pipe in memoria. E 'anche sorprendentemente lenta, prendendo un tempo molto lungo per leggere davvero tutto in

Quindi la mia domanda è di due parti:.

  1. Come posso farlo elaborare la linea di flusso per riga e non mantenere l'intero cosa ha bufferizzato nella memoria? Vorrei evitare di utilizzare diversi concerti di RAM per questo scopo.
  2. Come posso farlo funzionare più velocemente? PowerShell che itera su un get-content sembra essere 100 volte più lento di uno script C#.

spero ci sia qualcosa di stupido che sto facendo qui, come manca un parametro -LineBufferSize o qualcosa del genere ...

+8

Per velocizzare 'get-content', impostare -ReadCount su 512. Si noti che a questo punto $ _ nel Foreach sarà un array di stringhe. –

+1

Ancora, vorrei andare con il suggerimento di Romano di utilizzare il lettore .NET - molto più veloce. –

+0

Per curiosità, cosa succede se non mi interessa la velocità, ma solo la memoria? Molto probabilmente andrò con il suggerimento del lettore .NET, ma sono anche interessato a sapere come impedirlo dal buffering dell'intero pipe in memoria. – scobi

risposta

79

se siete davvero in procinto di lavorare su file di testo multi-gigabyte allora non usare PowerShell. Anche se trovi un modo per leggerlo, l'elaborazione più veloce di enormi quantità di linee sarà comunque lenta in PowerShell e non puoi evitarlo. Anche semplici cicli sono costosi, dicono per 10 milioni di iterazioni (abbastanza reali nel tuo caso) abbiamo:

# "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: Se siete ancora non ha paura quindi provare a utilizzare il lettore di .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

Non ci sono commenti su forse meglio/code più brevi. Non c'è niente di sbagliato nel codice originale con for e non è pseudo-codice. Ma il più breve (breve?) Variante del circuito di lettura è

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

Cosa c'è che non va nella parte originale? È solo un dato di fatto. – stej

+3

FYI, la compilazione di script in PowerShell V3 migliora leggermente la situazione. Il ciclo "vero lavoro" è passato da 117 secondi a V2 a 62 secondi su V3 digitato sulla console. Quando inserisco il loop in uno script e misuro l'esecuzione dello script su V3, scende a 34 secondi. –

+0

Ho inserito tutti e tre i test in uno script e ho ottenuto questi risultati: V3 Beta: 20/27/83 secondi; V2: 14/21/101. Sembra che nel mio esperimento V3 sia più veloce nel test 3, ma è piuttosto lento nei primi due. Bene, è Beta, speriamo che le prestazioni saranno migliorate in RTM. –

47

System.IO.File.ReadLines() è perfetto per questo scenario. Restituisce tutte le righe di un file, ma consente di iniziare immediatamente l'iterazione sulle righe, il che significa che non è necessario memorizzare l'intero contenuto in memoria.

Richiede .NET 4.0 o versione successiva.

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

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

+6

È necessaria una nota: .NET Framework - Supportato in: 4.5, 4. Pertanto, questo potrebbe non funzionare in V2 o V1 su alcune macchine. –

+0

Questo mi ha dato System.IO.File non esiste errore, ma il codice di cui sopra di Roman ha funzionato per me –

9

Se si desidera utilizzare direttamente PowerShell controllare il codice qui sotto.

+13

Questo è ciò che l'OP voleva eliminare perché 'Get-Content' è molto lento su file di grandi dimensioni. –

Problemi correlati