如何在 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 中将计算值写入 SQL INSERT/UPDATE
如何在powershell中将嵌套的任意关联数组值设置为.psd1文件?