web-dev-qa-db-fra.com

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

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

Malheureusement, get-content | %{ whatever($_) } semble conserver en mémoire l'ensemble des lignes à ce stade du canal. Il est aussi étonnamment lent et prend beaucoup de temps à tout lire.

Donc ma question est en deux parties:

  1. Comment puis-je lui faire traiter le flux ligne par ligne sans conserver la totalité de la mémoire en mémoire tampon? Je voudrais éviter d’utiliser plusieurs concerts de RAM à cette fin.
  2. Comment puis-je le faire courir plus vite? PowerShell itérant sur un get-content semble être 100 fois plus lent qu'un script C #.

J'espère que je fais quelque chose de stupide ici, comme rater un paramètre -LineBufferSize ou quelque chose comme ça ...

87
scobi

Si vous êtes vraiment sur le point de travailler sur des fichiers texte de plusieurs gigaoctets, n’utilisez pas PowerShell. De toute façon, même si vous trouvez un moyen de le lire plus rapidement, le traitement d’un grand nombre de lignes sera lent dans PowerShell et vous ne pouvez pas l’éviter. Même les boucles simples coûtent cher, par exemple pour 10 millions d'itérations (ce qui est bien 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' } }

PDATE: 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()
}

PDATE 2

Il y a des commentaires sur un code éventuellement meilleur/plus court. Il n'y a rien de mal avec le code original avec for et ce n'est pas du 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
}
90
Roman Kuzmin

System.IO.File.ReadLines() est parfait pour ce scénario. Il renvoie toutes les lignes d'un fichier, mais vous permet de commencer immédiatement à parcourir les lignes, ce qui signifie qu'il n'est pas nécessaire de stocker tout le contenu en mémoire.

Nécessite .NET 4.0 ou supérieur.

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

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

49
Despertar

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
}
6
Chris Blydenstein