Lire le fichier ligne par ligne dans PowerShell

100

Je souhaite lire un fichier ligne par ligne dans PowerShell. Plus précisément, je souhaite parcourir le fichier, stocker chaque ligne dans une variable de la boucle et effectuer un traitement sur la ligne.

Je connais l'équivalent Bash:

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

Pas beaucoup de documentation sur les boucles PowerShell.

Kingamere
la source
La réponse choisie par Mathias n'est pas une excellente solution. Get-Contentcharge le fichier entier en mémoire à la fois, ce qui échouera ou se bloquera sur les gros fichiers.
Kolob Canyon
1
@KolobCanyon qui est complètement faux. Par défaut, Get-Content charge chaque ligne comme un objet dans le pipeline. Si vous dirigez vers une fonction qui ne spécifie pas de processbloc et crache un autre objet par ligne dans le pipeline, alors cette fonction est le problème. Les problèmes de chargement du contenu complet en mémoire ne sont pas la faute de Get-Content.
The Fish
@TheFish foreach($line in Get-Content .\file.txt)Il chargera le fichier entier en mémoire avant de commencer l'itération. Si vous ne me croyez pas, allez chercher un fichier journal de 1 Go et essayez-le.
Kolob Canyon du
2
@KolobCanyon Ce n'est pas ce que vous avez dit. Vous avez dit que Get-Content charge tout en mémoire, ce qui n'est pas vrai. Votre exemple modifié de foreach serait, oui; foreach n'est pas conscient du pipeline. Get-Content .\file.txt | ForEach-Object -Process {}est compatible avec le pipeline et ne chargera pas le fichier entier en mémoire. Par défaut, Get-Content passera une ligne à la fois dans le pipeline.
The Fish

Réponses:

176

Pas beaucoup de documentation sur les boucles PowerShell.

Documentation sur les boucles dans PowerShell est abondante, et vous pouvez consulter les rubriques d'aide suivantes: about_For, about_ForEach, about_Do, about_While.

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

Une autre solution PowerShell idiomatique à votre problème consiste à diriger les lignes du fichier texte vers l' ForEach-Objectapplet de commande :

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

Au lieu de faire correspondre les expressions régulières à l'intérieur de la boucle, vous pouvez faire passer les lignes Where-Objectpour filtrer uniquement ceux qui vous intéressent:

Get-Content .\file.txt | Where-Object {$_ -match $regex} | ForEach-Object {
    # Work here
}
Mathias R. Jessen
la source
Les liens ne sont pas rompus, mais ils redirigent maintenant vers docs.microsoft.com.
Peter Mortensen
@KolobCanyon qui n'a jamais été mentionné comme un problème sur l'OP.
The Fish
53

Get-Contenta de mauvaises performances; il essaie de lire le fichier en mémoire en une seule fois.

Le lecteur de fichiers C # (.NET) lit chaque ligne une par une

Meilleure performance

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

Ou un peu moins performant

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

La foreachdéclaration sera probablement légèrement plus rapide que ForEach-Object(voir les commentaires ci-dessous pour plus d'informations).

Canyon de Kolob
la source
5
J'utiliserais probablement [System.IO.File]::ReadLines("C:\path\to\file.txt") | ForEach-Object { ... }. L' foreachinstruction chargera la collection entière dans un objet . ForEach-Objectutilise un pipeline pour diffuser. Maintenant, l' foreachinstruction sera probablement légèrement plus rapide que la ForEach-Objectcommande, mais c'est parce que le chargement de l'ensemble en mémoire est généralement plus rapide. Get-Contentest toujours terrible, cependant.
Bacon Bits
@BaconBits foreach()est un alias deForeach-Object
Kolob Canyon
16
C'est une idée fausse très courante. foreachest une déclaration, comme if, forou while. ForEach-Objectest une commande, comme Get-ChildItem. Il existe également un alias par défaut de foreachfor ForEach-Object, mais il n'est utilisé que lorsqu'il existe un pipeline. Voir la longue explication dans Get-Help about_Foreach, ou cliquez sur le lien dans mon commentaire précédent qui mène à un article entier de The Scripting Guys de Microsoft sur les différences entre la déclaration et la commande.
Bacon Bits
4
@BaconBits blogs.technet.microsoft.com/heyscriptingguy/2014/07/08/ ... J'ai appris quelque chose de nouveau. Merci. J'ai supposé qu'ils étaient les mêmes parce que Get-Alias foreach=> Foreach-Object, mais vous avez raison, il y a des différences
Kolob Canyon
2
Cela fonctionne, mais vous aurez envie de changer $linepour $_dans le bloc de script de la boucle.
Bacon Bits
1

L'interrupteur tout-puissant fonctionne bien ici:

'one
two
three' > file

$regex = '^t'

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

Production:

line is two
line is three
js2010
la source