87 votes

Comment traiter un fichier dans PowerShell ligne par ligne en tant que flux

Je travaille avec des fichiers texte de plusieurs gigaoctets et je souhaite effectuer un traitement en continu sur ces fichiers à l'aide de PowerShell. C'est simple, il suffit d'analyser chaque ligne et d'en extraire des données, puis de les stocker dans une base de données.

Malheureusement, get-content | %{ whatever($_) } semble garder en mémoire l'ensemble des lignes à ce stade du tuyau. Il est aussi étonnamment lent, prenant un temps très long pour tout lire.

Ma question est donc en deux parties :

  1. Comment puis-je faire en sorte qu'il traite le flux ligne par ligne et ne garde pas l'ensemble en mémoire tampon ? J'aimerais éviter d'utiliser plusieurs gigas de RAM à cette fin.
  2. Comment puis-je le faire fonctionner plus rapidement ? PowerShell itérant sur un get-content semble être 100x plus lent qu'un script en C#.

J'espère qu'il y a quelque chose de stupide que je fais ici, comme manquer un -LineBufferSize paramètre ou autre...

90voto

Roman Kuzmin Points 13913

Si vous êtes vraiment sur le point de travailler sur des fichiers texte de plusieurs gigaoctets, n'utilisez pas PowerShell. Même si vous trouvez un moyen de les lire plus rapidement, le traitement d'un grand nombre de lignes sera de toute façon lent dans PowerShell et vous ne pouvez pas l'éviter. Même les boucles simples sont coûteuses, disons que pour 10 millions d'itérations (tout à fait réel dans votre cas) nous avons :

# "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 : Si vous n'avez toujours pas peur, essayez d'utiliser le lecteur .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()
}

MISE À JOUR 2

Il y a des commentaires sur la possibilité d'un code meilleur/plus court. Il n'y a rien de mal dans le code original avec for et ce n'est pas un pseudo-code. Mais la variante la plus courte (la plus courte ?) de la boucle de lecture est

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

49voto

Despertar Points 5365

System.IO.File.ReadLines() est parfait pour ce scénario. Elle renvoie toutes les lignes d'un fichier, mais vous permet de commencer à itérer sur les lignes immédiatement, ce qui signifie qu'elle n'a pas besoin de stocker le contenu entier en mémoire.

Nécessite .NET 4.0 ou plus.

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

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

6voto

Si vous souhaitez utiliser directement PowerShell, consultez le code ci-dessous.

$content = Get-Content C:\Users\You\Documents\test.txt
foreach ($line in $content)
{
    Write-Host $line
}

0voto

larkee Points 106

Juste avec les cmdlets PowerShell, par exemple :

$content = Get-Content D:\output.reg
$i = 0
$content | ForEach-Object {Write-Host ($i++) $_;}

Il vous donne :

0 Windows Registry Editor Version 5.00
1 
2 
3 [HKEY_CURRENT_USER\Printers\Connections]
4 
5 
6 [HKEY_CURRENT_USER\Printers\Connections\,,PS3,P3 P1]
7 "GuidPrinter"="{F4495FDF-B690-4E82-9D68-3A1D1A7E31B1}"
8 "LocalConnection"=dword:00000001
9 "Provider"="win32spl.dll"
10 "Server"="\\\\PS3"

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X