如何在 PowerShell 中将文件作为流逐行处理

Posted

技术标签:

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

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

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

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

    如何让它逐行处理流,而不是将整个内容缓存在内存中?我想避免为此使用几 GB 的 RAM。 如何让它运行得更快? 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 | % $_ 将不起作用... 【参考方案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 在大文件上非常慢。

以上是关于如何在 PowerShell 中将文件作为流逐行处理的主要内容,如果未能解决你的问题,请参考以下文章

如何在powershell中将变量放入文件名

如何在 PowerShell 中将计算值写入 SQL INSERT/UPDATE

如何在powershell中将嵌套的任意关联数组值设置为.psd1文件?

awk编程语言

PowerShell:如何在 PowerShell 中将数组对象转换为字符串?

如何读取文本文件(逐行)并使用输出作为 nameS 在 java 中创建 .pdf 文件?