9

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

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

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

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 流编写器?

我可以为此使用 (New-Item "path" -type "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,但这是另一个问题!

4

2 回答 2

14

阅读

至于读取文件和解析,我会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 $_}
}

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

  • 支持正则表达式,您不必制作子字符串(这可能很昂贵)并且
  • 该参数 -file非常方便;)

写作

至于写输出,我会测试使用streamwriter,但是如果Add-Content你的性能不错,我会坚持下去。

补充:Keith 建议使用>>operator,但是,它似乎很慢。除此之外,它以 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. 如果代码清晰很重要,请使用Add-Content.


子字符串与正则表达式

根据 Keith 的说法,Substring 快了 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

所以区别并不重要,对我来说,正则表达式更具可读性。

于 2010-01-29T06:06:30.137 回答
3

鉴于输入文件的大小,您肯定希望一次处理一行。我认为重新打开/关闭输出文件不会对性能造成太大影响。它确实使使用管道甚至作为单线实现成为可能 - 与您的实现并没有太大不同。我将它包装在这里以摆脱水平滚动条:

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}}}
于 2010-01-29T04:37:20.567 回答