Dans Powershell, Quel est le moyen le plus efficace de diviser un gros fichier texte par type d'enregistrement?

J'utilise Powershell pour certains travaux D'ETL, en lisant des fichiers texte compressés et en les divisant en fonction des trois premiers caractères de chaque ligne.

si Je ne faisais que filtrer le fichier d'entrée, je pourrais pomper le flux filtré vers L'extérieur et en finir avec lui. Mais je dois rediriger la sortie vers plus d'une destination, et pour autant que je sache, cela ne peut pas être fait avec un simple tuyau. J'utilise déjà un .net streamreader pour lire les fichiers d'entrée compressés., et je me demande si je dois utiliser un streamwriter pour écrire les fichiers de sortie aussi.

la version naïve ressemble à quelque chose comme ceci:

while (!$reader.EndOfFile) {
  $line = $reader.ReadLine();
  switch ($line.substring(0,3) {
    "001" {Add-Content "output001.txt" $line}
    "002" {Add-Content "output002.txt" $line}
    "003" {Add-Content "output003.txt" $line}
    }
  }

qui ressemble juste à de mauvaises nouvelles: trouver, ouvrir, écrire et fermer un dossier une fois par rangée. Les fichiers d'entrée sont énormes 500 MO+ monstres.

y a-t-il une façon idiomatique de gérer efficacement ces constructions W/ Powershell, ou dois-je me tourner vers le .net streamwriter?

y a-t-il des méthodes d'un objet (New-Item" path "- type" file") que je pourrais utiliser pour cela?

MODIFIER pour le contexte:

j'utilise la bibliothèque DotNetZip pour lire des fichiers ZIP en tant que flux; donc streamreader plutôt que Get-Content / gc . Code échantillon:

[System.Reflection.Assembly]::LoadFrom("PathToIonic.Zip.dll") 
$zipfile = [Ionic.Zip.ZipFile]::Read("PathToFile.zip")

foreach ($entry in $zipfile) {
  $reader = new-object system.io.streamreader $entry.OpenReader();
  while (!$reader.EndOfFile) {
    $line = $reader.ReadLine();
    #do something here
  }
}

je devrais probablement Dispose() des deux $ zipfile et $ reader, mais c'est pour un autre question!

9
demandé sur Peter Radocchia 2010-01-29 06:44:24

2 réponses

Lire

pour ce qui est de la lecture du fichier et de l'analyse, je dirais switch déclaration:

switch -file c:\temp\stackoverflow.testfile2.txt -regex {
  "^001" {Add-Content c:\temp\stackoverflow.testfile.001.txt $_}
  "^002" {Add-Content c:\temp\stackoverflow.testfile.002.txt $_}
  "^003" {Add-Content c:\temp\stackoverflow.testfile.003.txt $_}
}

je pense que c'est une meilleure approche, car

  • il ya un soutien pour les regex, vous n'avez pas il faut faire des substrats (qui pourraient être cher) et
  • le paramètre -file est assez pratique;)

Écrit

quant à l'écriture de la sortie, je vais tester pour utiliser streamwriter, cependant si la performance de Add-Content est décente pour vous, je m'y tiendrais.

ajouté: Keith a proposé d'utiliser l'opérateur >> , mais il semble qu'il soit très lent. En outre, il écrit la sortie en Unicode qui double la taille du fichier.

Regardez mon test:

[1]: (measure-command {
>>     gc c:\temp\stackoverflow.testfile2.txt  | %{$c = $_; switch ($_.Substring(0,3)) {
>>             '001'{$c >> c:\temp\stackoverflow.testfile.001.txt} `
>>             '002'{$c >> c:\temp\stackoverflow.testfile.002.txt} `
>>             '003'{$c >> c:\temp\stackoverflow.testfile.003.txt}}}
>> }).TotalSeconds
>>
159,1585874
[2]: (measure-command {
>>     gc c:\temp\stackoverflow.testfile2.txt  | %{$c = $_; switch ($_.Substring(0,3)) {
>>             '001'{$c | Add-content c:\temp\stackoverflow.testfile.001.txt} `
>>             '002'{$c | Add-content c:\temp\stackoverflow.testfile.002.txt} `
>>             '003'{$c | Add-content c:\temp\stackoverflow.testfile.003.txt}}}
>> }).TotalSeconds
>>
9,2696923

la différence est énorme .

juste pour comparaison:

[3]: (measure-command {
>>     $reader = new-object io.streamreader c:\temp\stackoverflow.testfile2.txt
>>     while (!$reader.EndOfStream) {
>>         $line = $reader.ReadLine();
>>         switch ($line.substring(0,3)) {
>>             "001" {Add-Content c:\temp\stackoverflow.testfile.001.txt $line}
>>             "002" {Add-Content c:\temp\stackoverflow.testfile.002.txt $line}
>>             "003" {Add-Content c:\temp\stackoverflow.testfile.003.txt $line}
>>             }
>>         }
>>     $reader.close()
>> }).TotalSeconds
>>
8,2454369
[4]: (measure-command {
>>     switch -file c:\temp\stackoverflow.testfile2.txt -regex {
>>         "^001" {Add-Content c:\temp\stackoverflow.testfile.001.txt $_}
>>         "^002" {Add-Content c:\temp\stackoverflow.testfile.002.txt $_}
>>         "^003" {Add-Content c:\temp\stackoverflow.testfile.003.txt $_}
>>     }
>> }).TotalSeconds
8,6755565

ajouté: j'étais curieux de la performance d'écriture .. et j'ai été un peu surpris

[8]: (measure-command {
>>     $sw1 = new-object io.streamwriter c:\temp\stackoverflow.testfile.001.txt3b
>>     $sw2 = new-object io.streamwriter c:\temp\stackoverflow.testfile.002.txt3b
>>     $sw3 = new-object io.streamwriter c:\temp\stackoverflow.testfile.003.txt3b
>>     switch -file c:\temp\stackoverflow.testfile2.txt -regex {
>>         "^001" {$sw1.WriteLine($_)}
>>         "^002" {$sw2.WriteLine($_)}
>>         "^003" {$sw3.WriteLine($_)}
>>     }
>>     $sw1.Close()
>>     $sw2.Close()
>>     $sw3.Close()
>>
>> }).TotalSeconds
>>
0,1062315

c'est 80 fois plus rapide . Maintenant, vous vous devez décider si la vitesse est importante, utilisez StreamWriter . Si la clarté du code est importante, utilisez Add-Content .


Substring vs. Regex

selon pour Keith Substring est 20% plus rapide. Elle dépend, comme toujours. Cependant, dans mon cas, les résultats sont comme ceci:

[102]: (measure-command {
>>     gc c:\temp\stackoverflow.testfile2.txt  | %{$c = $_; switch ($_.Substring(0,3)) {
>>             '001'{$c | Add-content c:\temp\stackoverflow.testfile.001.s.txt} `
>>             '002'{$c | Add-content c:\temp\stackoverflow.testfile.002.s.txt} `
>>             '003'{$c | Add-content c:\temp\stackoverflow.testfile.003.s.txt}}}
>> }).TotalSeconds
>>
9,0654496
[103]: (measure-command {
>>     gc c:\temp\stackoverflow.testfile2.txt  | %{$c = $_; switch -regex ($_) {
>>             '^001'{$c | Add-content c:\temp\stackoverflow.testfile.001.r.txt} `
>>             '^002'{$c | Add-content c:\temp\stackoverflow.testfile.002.r.txt} `
>>             '^003'{$c | Add-content c:\temp\stackoverflow.testfile.003.r.txt}}}
>> }).TotalSeconds
>>
9,2563681

donc la différence n'est pas importante et pour moi, les regexes sont plus lisibles.

14
répondu stej 2010-01-29 13:00:26

étant donné la taille des fichiers d'entrée, vous voulez certainement traiter une ligne à la fois. Je ne pense pas que la réouverture/fermeture des fichiers de sortie serait trop énorme un coup de perf. Il rend certainement la mise en œuvre possible en utilisant le pipeline même comme une doublure-vraiment pas trop différent de votre impll. Je l'ai enveloppé ici pour me débarrasser de la barre de défilement horizontale:

gc foo.log | %{switch ($_.Substring(0,3)) {
    '001'{$input | out-file output001.txt -enc ascii -append} `
    '002'{$input | out-file output002.txt -enc ascii -append} `
    '003'{$input | out-file output003.txt -enc ascii -append}}}
3
répondu Keith Hill 2010-01-29 06:52:16