代码之家  ›  专栏  ›  技术社区  ›  Peter Radocchia

在PowerShell中,按记录类型拆分大型文本文件的最有效方法是什么?

  •  9
  • Peter Radocchia  · 技术社区  · 15 年前

    我正在使用PowerShell进行一些ETL工作,读取压缩文本文件,并根据每行的前三个字符将其拆分。

    如果我只是过滤输入文件,我可以通过管道将过滤后的流传输到输出文件,并完成处理。但我需要将输出重定向到多个目的地,据我所知,这不能用简单的管道完成。我已经在使用.NET StreamReader读取压缩的输入文件,我想知道是否也需要使用StreamWriter来写入输出文件。

    天真的版本看起来像这样:

    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}
        }
      }
    

    这看起来像是坏消息:每行查找、打开、写入和关闭一个文件。输入文件是巨大的500MB+怪物。

    有没有一种惯用的方法可以通过PowerShell构造有效地处理这个问题,或者我应该转向.NET流作者?

    是否有我可以用于此的(新项“path”-类型“file”)对象的方法?

    编辑上下文:

    我正在使用 DotNetZip 将zip文件作为流读取的库;因此 streamreader 而不是 Get-Content / gc .样本代码:

    [System.Reflection.Assembly]::LoadFrom("\Path\To\Ionic.Zip.dll") 
    $zipfile = [Ionic.Zip.ZipFile]::Read("\Path\To\File.zip")
    
    foreach ($entry in $zipfile) {
      $reader = new-object system.io.streamreader $entry.OpenReader();
      while (!$reader.EndOfFile) {
        $line = $reader.ReadLine();
        #do something here
      }
    }
    

    我可能应该 Dispose() 对于$zipfile和$reader,这是另一个问题!

    2 回复  |  直到 11 年前
        1
  •  14
  •   stej    15 年前

    阅读

    至于读取文件和解析,我将继续 switch 声明:

    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 $_}
    }
    

    我认为这是更好的方法,因为

    • 有对regex的支持,你不支持 必须生成子字符串(可能 昂贵)和
    • 参数 -file 非常方便;)

    写作

    至于编写输出,我将测试使用streamwriter,但是如果 Add-Content 对你来说很好,我会坚持的。

    补充: 基思提议使用 >> 但是,操作人员似乎很慢。此外,它以unicode编写输出,使文件大小加倍。

    看看我的测试:

    [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
    

    区别在于 巨大的 .

    仅作比较:

    [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
    

    补充:我对写作表现很好奇。我有点惊讶

    [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
    

    它是 快80倍 . 现在你必须决定-如果速度很重要,使用 StreamWriter . 如果代码清晰性很重要,请使用 添加内容 .


    子字符串与正则表达式

    根据基思的说法,子串速度快20%。这要视情况而定,一如既往。但是,在我的例子中,结果如下:

    [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
    

    所以区别并不重要,对我来说,正则表达式更易于阅读。

        2
  •  3
  •   Keith Hill    15 年前

    考虑到输入文件的大小,您肯定希望一次处理一行。我不认为重新打开/关闭输出文件会对性能造成太大的影响。它当然可以使使用管道的实现成为可能,即使是作为一个内衬——与您的IMPL没有太大的不同。我把它包装在这里以去掉水平滚动条:

    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}}}