【问题标题】:How to process a file in PowerShell line-by-line as a stream如何在 PowerShell 中将文件作为流逐行处理
【发布时间】:2011-05-10 16:12:55
【问题描述】:

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

不幸的是,get-content | %{ whatever($_) } 似乎将管道此阶段的整个行集保留在内存中。它的速度也出奇的慢,需要很长时间才能真正读完。

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

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

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

【问题讨论】:

  • 要加快get-content 的速度,请将 -ReadCount 设置为 512。注意此时,Foreach 中的 $_ 将是一个字符串数组。
  • 不过,我还是会接受 Roman 的建议,即使用 .NET 阅读器——要快得多。
  • 出于好奇,如果我不关心速度而只关心内存会怎样?我很可能会接受 .NET 阅读器的建议,但我也很想知道如何防止它在内存中缓冲整个管道。
  • 为了尽量减少缓冲,请避免将Get-Content 的结果分配给变量,因为这会将整个文件加载到内存中。默认情况下,在管道中,Get-Content 一次处理一行文件。只要您不累积结果或使用内部累积的 cmdlet(如 Sort-Object 和 Group-Object),那么内存命中应该不会太糟糕。 Foreach-Object (%) 是一种处理每一行的安全方法,一次处理一个。
  • @dwarfsoft 这没有任何意义。 -End 块仅在所有处理完成后运行一次。您可以看到,如果您尝试使用get-content | % -End { },那么它会抱怨,因为您没有提供进程块。所以它不能默认使用-End,它必须默认使用-Process。并尝试1..5 | % -process { } -end { 'q' } 并看到结束块只发生一次,如果脚本块默认为 -End...,通常的gc | % { $_ } 将不起作用...

标签: powershell stream


【解决方案1】:

如果您真的要处理数 GB 的文本文件,请不要使用 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

有一些关于可能更好/更短的代码的 cmets。 for 的原始代码没有任何问题,它不是伪代码。但是阅读循环的较短(最短?)变体是

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

【讨论】:

  • 仅供参考,PowerShell V3 中的脚本编译稍微改善了这种情况。 “真正的工作”循环从 V2 上的 117 秒变为 V3 上在控制台输入的 62 秒。当我将循环放入脚本并在 V3 上测量脚本执行时,它下降到 34 秒。
  • 哎呀,应该是 -ne 表示不相等。那个特定的 do..while 循环的问题是文件末尾的 null 将被处理(在本例中为输出)。要解决这个问题,您也可以使用for ( $line = $reader.ReadLine(); $line -ne $null; $line = $reader.ReadLine() ) { $line }
  • @BeowulfNode42,我们可以做得更短:while($null -ne ($line = $read.ReadLine())) {$line}。但这个话题并不是关于这些事情的。
  • @RomanKuzmin +1 你评论的while-loop sn-p,它很容易理解并且会是一个很好的答案。但是,您对 for(;;) 的实际回答让我感到困惑,它是伪代码还是实际上合法的 powershell 语法?如果您想详细说明一下,非常感谢。
  • for() 表示无限循环
【解决方案2】:

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

【讨论】:

  • 需要注意:.NET Framework - 受支持:4.5、4。因此,在某些机器上,这可能不适用于 V2 或 V1。
  • 这给了我 System.IO.File does not exist 错误,但 Roman 上面的代码对我有用
  • 这正是我所需要的,而且很容易直接放入现有的 powershell 脚本中。
【解决方案3】:

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

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

【讨论】:

  • 这就是 OP 想要摆脱的,因为 Get-Content 在大文件上非常慢。
猜你喜欢
  • 2017-07-28
  • 2018-08-23
  • 2018-01-18
  • 2019-12-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-02-04
相关资源
最近更新 更多