Estoy trabajando con algunos archivos de texto de varios gigabytes y quiero hacer un procesamiento de transmisión en ellos usando PowerShell. Es algo simple, simplemente analizar cada línea y extraer algunos datos, luego almacenarlos en una base de datos.
Desafortunadamente, get-content | %{ whatever($_) }
parece mantener todo el conjunto de líneas en esta etapa de la tubería en la memoria. También es sorprendentemente lento, y lleva mucho tiempo leerlo todo.
Entonces mi pregunta tiene dos partes:
- ¿Cómo puedo hacer que procese la secuencia línea por línea y no mantenga todo en búfer en la memoria? Me gustaría evitar utilizar varios gigas de RAM para este propósito.
- ¿Cómo puedo hacer que funcione más rápido? La iteración de PowerShell sobre un
get-content
parece ser 100 veces más lenta que un script de C #.
Espero que haya algo tonto que estoy haciendo aquí, como perder un -LineBufferSize
parámetro o algo ...
powershell
stream
Scobi
fuente
fuente
get-content
, establezca -ReadCount en 512. Tenga en cuenta que en este punto, $ _ en Foreach será una matriz de cadenas.Get-Content
a una variable, ya que cargará todo el archivo en la memoria. De forma predeterminada, en una pipleline,Get-Content
procesa el archivo una línea a la vez. Siempre que no esté acumulando los resultados o utilizando un cmdlet que se acumula internamente (como Sort-Object y Group-Object), el impacto de la memoria no debería ser tan malo. Foreach-Object (%) es una forma segura de procesar cada línea, una a la vez.get-content | % -End { }
, se queja porque no ha proporcionado un bloque de proceso. Por lo tanto, no puede usar -End por defecto, debe usar -Process por defecto. Y trate de1..5 | % -process { } -end { 'q' }
ver que el bloque final solo ocurre una vez, lo habitualgc | % { $_ }
no funcionaría si el bloque de secuencia de comandos estuviera predeterminado en -End ...Respuestas:
Si realmente está a punto de trabajar con archivos de texto de varios gigabytes, no utilice PowerShell. Incluso si encuentra una manera de leerlo, el procesamiento más rápido de una gran cantidad de líneas será lento en PowerShell de todos modos y no puede evitarlo. Incluso los bucles simples son costosos, digamos para 10 millones de iteraciones (bastante reales en su caso) tenemos:
# "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' } }
ACTUALIZACIÓN: Si aún no tiene miedo, intente usar el lector .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() }
ACTUALIZACIÓN 2
Hay comentarios sobre un código posiblemente mejor / más corto. No hay nada de malo con el código original
for
y no es un pseudocódigo. Pero la variante más corta (¿más corta?) Del ciclo de lectura es$reader = [System.IO.File]::OpenText("my.log") while($null -ne ($line = $reader.ReadLine())) { $line }
fuente
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}
. Pero el tema no se trata realmente de esas cosas.System.IO.File.ReadLines()
es perfecto para este escenario. Devuelve todas las líneas de un archivo, pero le permite comenzar a iterar sobre las líneas inmediatamente, lo que significa que no tiene que almacenar todo el contenido en la memoria.Requiere .NET 4.0 o superior.
foreach ($line in [System.IO.File]::ReadLines($filename)) { # do something with $line }
http://msdn.microsoft.com/en-us/library/dd383503.aspx
fuente
Si desea utilizar PowerShell directo, consulte el siguiente código.
$content = Get-Content C:\Users\You\Documents\test.txt foreach ($line in $content) { Write-Host $line }
fuente
Get-Content
es muy lento en archivos grandes.