Leer archivo línea por línea en PowerShell

100

Quiero leer un archivo línea por línea en PowerShell. Específicamente, quiero recorrer el archivo, almacenar cada línea en una variable en el bucle y hacer algún procesamiento en la línea.

Sé el equivalente de Bash:

while read line do
    if [[ $line =~ $regex ]]; then
          # work here
    fi
done < file.txt

No hay mucha documentación sobre los bucles de PowerShell.

Kingamere
fuente
La respuesta seleccionada de Mathias no es una gran solución. Get-Contentcarga todo el archivo en la memoria a la vez, lo que fallará o se congelará en archivos grandes.
Kolob Canyon
1
@KolobCanyon eso es completamente falso. De forma predeterminada, Get-Content carga cada línea como un objeto en la canalización. Si está conectando a una función que no especifica un processbloque y escupe otro objeto por línea en la canalización, entonces esa función es el problema. Cualquier problema con la carga del contenido completo en la memoria no es culpa de Get-Content.
The Fish
@TheFish Cargará foreach($line in Get-Content .\file.txt)el archivo completo en la memoria antes de que comience a iterar. Si no me cree, obtenga un archivo de registro de 1 GB y pruébelo.
Kolob Canyon
2
@KolobCanyon Eso no es lo que dijiste. Dijiste que Get-Content lo carga todo en la memoria, lo cual no es cierto. Su ejemplo cambiado de foreach lo haría, sí; foreach no es consciente de la canalización. Get-Content .\file.txt | ForEach-Object -Process {}es compatible con la canalización y no cargará todo el archivo en la memoria. De forma predeterminada, Get-Content pasará una línea a la vez a través de la canalización.
The Fish

Respuestas:

176

No hay mucha documentación sobre los bucles de PowerShell.

Documentación sobre los bucles en PowerShell es mucha, y es posible que desee echa un vistazo a los siguientes temas: about_For, about_ForEach, about_Do, about_While.

foreach($line in Get-Content .\file.txt) {
    if($line -match $regex){
        # Work here
    }
}

Otra solución idiomática de PowerShell para su problema es canalizar las líneas del archivo de texto al ForEach-Objectcmdlet :

Get-Content .\file.txt | ForEach-Object {
    if($_ -match $regex){
        # Work here
    }
}

En lugar de hacer coincidir expresiones regulares dentro del bucle, puede canalizar las líneas Where-Objectpara filtrar solo aquellos que le interesan:

Get-Content .\file.txt | Where-Object {$_ -match $regex} | ForEach-Object {
    # Work here
}
Mathias R. Jessen
fuente
Los enlaces no están rotos, pero ahora redireccionan a docs.microsoft.com.
Peter Mortensen
@KolobCanyon que nunca se mencionó como un problema en el OP.
The Fish
52

Get-Contenttiene mal desempeño; intenta leer el archivo en la memoria de una vez.

El lector de archivos C # (.NET) lee cada línea una por una

Mejor actuación

foreach($line in [System.IO.File]::ReadLines("C:\path\to\file.txt"))
{
       $line
}

O un poco menos eficiente

[System.IO.File]::ReadLines("C:\path\to\file.txt") | ForEach-Object {
       $_
}

Es foreachprobable que la declaración sea un poco más rápida que ForEach-Object(consulte los comentarios a continuación para obtener más información).

Cañón de Kolob
fuente
5
Probablemente usaría [System.IO.File]::ReadLines("C:\path\to\file.txt") | ForEach-Object { ... }. La foreachdeclaración cargará toda la colección en un objeto . ForEach-Objectutiliza una canalización para transmitir. Ahora, la foreachdeclaración probablemente será un poco más rápida que el ForEach-Objectcomando, pero eso se debe a que cargar todo en la memoria generalmente es más rápido. Get-ContentSin embargo, sigue siendo terrible.
Tocino Bits
@BaconBits foreach()es un alias deForeach-Object
Kolob Canyon
15
Ese es un error muy común. foreaches una declaración, al igual que if, foro while. ForEach-Objectes un comando, como Get-ChildItem. También hay un alias predeterminado de foreachfor ForEach-Object, pero solo se usa cuando hay una canalización. Vea la explicación larga en Get-Help about_Foreach, o haga clic en el enlace en mi comentario anterior que va a un artículo completo de The Scripting Guys de Microsoft sobre las diferencias entre la declaración y el comando.
Bocados de tocino
3
@BaconBits blogs.technet.microsoft.com/heyscriptingguy/2014/07/08/… Aprendí algo nuevo. Gracias. Supuse que eran iguales porque Get-Alias foreach=> Foreach-Object, pero tienes razón, hay diferencias
Kolob Canyon
2
Eso funcionará, pero querrá cambiar $linea $_en el bloque de script del ciclo.
Tocino Bits
1

El interruptor todopoderoso funciona bien aquí:

'one
two
three' > file

$regex = '^t'

switch -regex -file file { 
  $regex { "line is $_" } 
}

Salida:

line is two
line is three
js2010
fuente