如何在PowerShell中逐行处理文件


87

我正在处理一些千兆字节的文本文件,并希望使用PowerShell对它们进行一些流处理。这很简单,只需解析每一行并提取一些数据,然后将其存储在数据库中即可。

不幸的是,get-content | %{ whatever($_) }似乎在管道的此阶段将整个行集保留在内存中。它也非常慢,要花很长时间才能读完所有内容。

所以我的问题分为两个部分:

  1. 如何使其逐行处理流,而不是将整个内容保留在内存中?我想避免为此目的用完几GB的RAM。
  2. 如何使其运行更快?在aget-content上进行PowerShell迭代似乎比C#脚本慢100倍。

我希望我在这里做一些愚蠢的事情,例如缺少-LineBufferSize参数或其他东西。


9
为了加快速度get-content,请将-ReadCount设置为512。请注意,此时,Foreach中的$ _将是一个字符串数组。
基思·希尔

1
尽管如此,我还是会同意Roman提出的使用.NET阅读器的建议-更快。
基思·希尔

出于好奇,如果我不在乎速度,而只在乎记忆,会发生什么?我很可能会接受.NET读者的建议,但是我也很想知道如何防止它在内存中缓冲整个管道。
scobi 2010年

7
为了最大程度地减少缓冲,请避免将结果分配给Get-Content变量,因为这会将整个文件加载到内存中。默认情况下,在管线中,一次只Get-Content处理一行文件。只要您不累积结果或使用内部累积的cmdlet(例如Sort-Object和Group-Object),内存命中率就不会太差。Foreach-Object(%)是一次处理每一行的安全方法。
基思·希尔

2
@dwarfsoft没有任何意义。在所有处理完成之后,-End块仅运行一次。您可以看到,如果您尝试使用get-content | % -End { }它,那么它将抱怨,因为您尚未提供流程块。因此,默认情况下不能使用-End,默认情况下必须使用-Process。并尝试1..5 | % -process { } -end { 'q' }查看结束块仅发生一次,gc | % { $_ }如果脚本块默认为-End ,则通常将不起作用...
TessellatingHeckler

Answers:


92

如果您真的要处理千兆字节的文本文件,请不要使用PowerShell。即使您找到一种读取它的方法,在PowerShell中无论如何,处理大量的行还是很慢的,您也无法避免这种情况。即使是简单的循环也很昂贵,例如,对于1000万次迭代(在您的情况下非常真实),我们有:

# "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' } }

更新:如果您仍然不害怕,请尝试使用.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()
}

更新2

有关于更好/更短代码的注释。原始代码没有问题,for它也不是伪代码。但是阅读循环的较短(最短?)变体是

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

3
仅供参考,PowerShell V3中的脚本编译可以稍微改善这种情况。“真实作业”循环从V2上的117秒变为V3在控制台上键入的62秒。当我将循环放入脚本并测量V3上的脚本执行时,它减少到34秒。
基思·希尔

我将所有三个测试放入脚本中,并获得了以下结果:V3 Beta:20/27/83秒;V2:14/21/101。看起来在我的实验V3中,测试3中的速度更快,但在前两个中却相当慢。好吧,它是Beta版,希望RTM能够提高性能。
罗曼·库兹明

人们为什么坚持使用这样的循环中断。为什么不使用不需要的循环,并读起来更好,例如用do { $line = $reader.ReadLine(); $line } while ($line -neq $null)
BeowulfNode42'forow

1
哎呀,应该是-ne代表不相等。该特定的do..while循环存在以下问题:将处理文件末尾的null(在这种情况下为输出)。要解决此问题,您也可以拥有for ( $line = $reader.ReadLine(); $line -ne $null; $line = $reader.ReadLine() ) { $line }
BeowulfNode42,2014年

4
@ BeowulfNode42,我们可以做到这一点甚至更短:while($null -ne ($line = $read.ReadLine())) {$line}。但是,话题并非真的与此类事情有关。
罗曼·库兹敏

51

System.IO.File.ReadLines()非常适合这种情况。它返回文件的所有行,但是让您立即开始遍历各行,这意味着它不必将所有内容存储在内存中。

需要.NET 4.0或更高版本。

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

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


6
需要注意事项:.NET Framework-受以下版本支持:4.5、4。因此,在某些计算机上的V2或V1中,此功能可能不起作用。
罗曼·库兹明

这给了我System.IO.File不存在错误,但是Roman上面的代码为我工作了
Kolob Canyon

这正是我所需要的,很容易直接放入现有的powershell脚本中。
user1751825

5

如果要使用直接的PowerShell,请查看以下代码。

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

16
这就是OP想要摆脱的原因,因为Get-Content大文件的速度非常慢。
Roman Kuzmin 2014年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.