Leia o arquivo linha por linha no PowerShell

100

Quero ler um arquivo linha por linha no PowerShell. Especificamente, quero fazer um loop no arquivo, armazenar cada linha em uma variável no loop e fazer algum processamento na linha.

Eu conheço o equivalente do Bash:

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

Não há muita documentação sobre loops do PowerShell.

Kingamere
fonte
A resposta selecionada de Mathias não é uma boa solução. Get-Contentcarrega o arquivo inteiro na memória de uma vez, o que irá falhar ou congelar em arquivos grandes.
Kolob Canyon
1
@KolobCanyon que é completamente falso. Por padrão, Get-Content carrega cada linha como um objeto no pipeline. Se você está canalizando para uma função que não especifica um processbloco e cospe outro objeto por linha no pipeline, então essa função é o problema. Quaisquer problemas com o carregamento de todo o conteúdo na memória não são culpa do Get-Content.
The Fish
@TheFish foreach($line in Get-Content .\file.txt)Ele carregará o arquivo inteiro na memória antes de começar a iteração. Se você não acredita em mim, pegue um arquivo de log de 1 GB e experimente.
Kolob Canyon
2
@KolobCanyon Não foi isso que você disse. Você disse que Get-Content carrega tudo na memória, o que não é verdade. Seu exemplo alterado de foreach seria, sim; foreach não reconhece pipeline. Get-Content .\file.txt | ForEach-Object -Process {}reconhece o pipeline e não carrega o arquivo inteiro na memória. Por padrão, Get-Content passará uma linha por vez no pipeline.
The Fish

Respostas:

176

Não há muita documentação sobre loops do PowerShell.

Documentação sobre loops em PowerShell é abundante, e você pode querer verificar os seguintes tópicos da Ajuda: about_For, about_ForEach, about_Do, about_While.

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

Outra solução idiomática do PowerShell para o seu problema é canalizar as linhas do arquivo de texto para o ForEach-Objectcmdlet :

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

Em vez de correspondência de regex dentro do loop, você pode canalizar as linhas Where-Objectpara filtrar apenas aquelas em que está interessado:

Get-Content .\file.txt | Where-Object {$_ -match $regex} | ForEach-Object {
    # Work here
}
Mathias R. Jessen
fonte
Os links não estão quebrados, mas agora redirecionam para docs.microsoft.com.
Peter Mortensen
@KolobCanyon que nunca foi mencionado como um problema no OP.
The Fish
52

Get-Contenttem desempenho ruim; ele tenta ler o arquivo na memória de uma vez.

O leitor de arquivos C # (.NET) lê cada linha uma por uma

Melhor Performance

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

Ou ligeiramente menos performante

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

A foreachinstrução provavelmente será um pouco mais rápida do que ForEach-Object(consulte os comentários abaixo para obter mais informações).

Kolob Canyon
fonte
5
Eu provavelmente usaria [System.IO.File]::ReadLines("C:\path\to\file.txt") | ForEach-Object { ... }. A foreachinstrução carregará toda a coleção para um objeto . ForEach-Objectusa um pipeline para transmitir. Agora, a foreachinstrução provavelmente será um pouco mais rápida do que o ForEach-Objectcomando, mas isso porque carregar tudo na memória geralmente é mais rápido. Get-Contentainda é terrível, no entanto.
Bacon Bits
@BaconBits foreach()é um pseudônimo deForeach-Object
Kolob Canyon
15
Esse é um equívoco muito comum. foreaché uma afirmação, como if, for, ou while. ForEach-Objecté um comando, como Get-ChildItem. Também há um alias padrão de foreachpara ForEach-Object, mas ele só é usado quando há um pipeline. Veja a longa explicação em Get-Help about_Foreachou clique no link em meu comentário anterior, que leva a um artigo inteiro da Equipe de Scripts da Microsoft sobre as diferenças entre a instrução e o comando.
Bacon Bits
3
@BaconBits blogs.technet.microsoft.com/heyscriptingguy/2014/07/08/… Aprendeu algo novo. Obrigado. Presumi que fossem iguais porque Get-Alias foreach=> Foreach-Object, mas você está certo, há diferenças
Kolob Canyon
2
Isso funcionará, mas você vai querer mudar $linepara $_no bloco de script do loop.
Bacon Bits de
1

O interruptor todo-poderoso funciona bem aqui:

'one
two
three' > file

$regex = '^t'

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

Resultado:

line is two
line is three
js2010
fonte