【问题标题】:How can I make this PowerShell script parse large files faster?如何让这个 PowerShell 脚本更快地解析大文件?
【发布时间】:2016-12-14 04:07:41
【问题描述】:

我有以下 PowerShell 脚本,它将为 ETL 解析一些非常大的文件。对于初学者,我的测试文件约为 30 MB。较大的文件预计约为 200 MB。所以我有几个问题。

下面的脚本可以运行,但处理一个 30 MB 的文件也需要很长时间。

PowerShell 脚本:

$path = "E:\Documents\Projects\ESPS\Dev\DataFiles\DimProductionOrderOperation"
$infile = "14SEP11_ProdOrderOperations.txt"
$outfile = "PROCESSED_14SEP11_ProdOrderOperations.txt"
$array = @()

$content = gc $path\$infile |
    select -skip 4 |
    where {$_ -match "[|].*[|].*"} |
    foreach {$_ -replace "^[|]","" -replace "[|]$",""}

$header = $content[0]

$array = $content[0]
for ($i = 1; $i -le $content.length; $i+=1) {
    if ($array[$i] -ne $content[0]) {$array += $content[$i]}
}

$array | out-file $path\$outfile -encoding ASCII

数据文件摘录:

---------------------------
|Data statistics|Number of|
|-------------------------|
|Records passed |   93,118|
---------------------------
02/14/2012                                                                                                                                                           Production Operations and Confirmations                                                                                                                                                              2
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Production Operations and Confirmations
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|ProductionOrderNumber|MaterialNumber                       |ModifiedDate|Plant|OperationRoutingNumber|WorkCenter|OperationStatus|IsActive|     WbsElement|SequenceNumber|OperationNumber|OperationDescription                    |OperationQty|ConfirmedYieldQty|StandardValueLabor|ActualDirectLaborHrs|ActualContractorLaborHrs|ActualOvertimeLaborHrs|ConfirmationNumber|
|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|180849518            |011255486L1                          |02/08/2012  |2101 |            9901123118|56B30     |I9902          |        |SOC10MA2302SOCJ31|              |0140           |Operation 1                             |          1 |               0 |              0.0 |                    |                499.990 |                      |        9908651250|
|180849518            |011255486L1                          |02/08/2012  |2101 |            9901123118|56B30     |I9902          |        |SOC10MA2302SOCJ31|14            |9916           |Operation 2                             |          1 |               0 |            499.0 |                    |                        |                      |        9908532289|
|181993564            |011255486L1                          |02/09/2012  |2101 |            9901288820|56B30     |I9902          |        |SOC10MD2302SOCJ31|14            |9916           |Operation 1                             |          1 |               0 |            499.0 |                    |                399.599 |                      |        9908498544|
|180885825            |011255486L1                          |02/08/2012  |2101 |            9901162239|56B30     |I9902          |        |SOC10MG2302SOCJ31|              |0150           |Operation 3                             |          1 |               0 |              0.0 |                    |                882.499 |                      |        9908099659|
|180885825            |011255486L1                          |02/08/2012  |2101 |            9901162239|56B30     |I9902          |        |SOC10MG2302SOCJ31|14            |9916           |Operation 4                             |          1 |               0 |            544.0 |                    |                        |                      |        9908858514|
|181638583            |990104460I0                          |02/10/2012  |2101 |            9902123289|56G99     |I9902          |        |SOC11MAR105SOCJ31|              |0160           |Operation 5                             |          1 |               0 |          1,160.0 |                    |                        |                      |        9914295010|
|181681218            |990104460B0                          |02/08/2012  |2101 |            9902180981|56G99     |I9902          |        |SOC11MAR328SOCJ31|0             |9910           |Operation 6                             |          1 |               0 |            916.0 |                    |                        |                      |        9914621885|
|181681036            |990104460I0                          |02/09/2012  |2101 |            9902180289|56G99     |I9902          |        |SOC11MAR108SOCJ31|              |0180           |Operation 8                             |          1 |               0 |              1.0 |                    |                        |                      |        9914619196|
|189938054            |011255486A2                          |02/10/2012  |2101 |            9999206805|5AD99     |I9902          |        |RS08MJ2305SOCJ31 |              |0599           |Operation 8                             |          1 |               0 |              0.0 |                    |                        |                      |        9901316289|
|181919894            |012984532A3                          |02/10/2012  |2101 |            9902511433|A199399Z  |I9902          |        |SOC12MCB101SOCJ31|0             |9935           |Operation 9                             |          1 |               0 |              0.5 |                    |                        |                      |        9916914233|
|181919894            |012984532A3                          |02/10/2012  |2101 |            9902511433|A199399Z  |I9902          |        |SOC12MCB101SOCJ31|22            |9951           |Operation 10                            |          1 |               0 |           68.080 |                    |                        |                      |        9916914224|

【问题讨论】:

标签: powershell


【解决方案1】:

您的脚本一次读取一行(慢!)并将几乎整个文件存储在内存中(大!)。

试试这个(未经广泛测试):

$path = "E:\Documents\Projects\ESPS\Dev\DataFiles\DimProductionOrderOperation"
$infile = "14SEP11_ProdOrderOperations.txt"
$outfile = "PROCESSED_14SEP11_ProdOrderOperations.txt"

$batch = 1000

[regex]$match_regex = '^\|.+\|.+\|.+'
[regex]$replace_regex = '^\|(.+)\|$'

$header_line = (Select-String -Path $path\$infile -Pattern $match_regex -list).line

[regex]$header_regex = [regex]::escape($header_line)

$header_line.trim('|') | Set-Content $path\$outfile

Get-Content $path\$infile -ReadCount $batch |
    ForEach {
             $_ -match $match_regex -NotMatch $header_regex -Replace $replace_regex ,'$1' | Out-File $path\$outfile -Append
    }

这是内存使用和速度之间的折衷。 -match-replace 运算符将在数组上工作,因此您可以一次过滤和替换整个数组,而不必遍历每条记录。 -readcount 将导致文件以 $batch 记录块的形式读取,因此您基本上一次读取 1000 条记录,对该批次进行匹配和替换,然后将结果附加到输出文件中。然后它会返回下一个 1000 条记录。增加 $batch 的大小应该会加快速度,但它会使用更多的内存。调整它以适合您的资源。

【讨论】:

  • 谢谢。我又看了一遍,做了一点调整。不确定这对性能有多大帮助,但如果我正确阅读文档,编译的正则表达式会更快,并且将变量转换为 [regex] 会导致它们被编译。
  • 这很好,但我还需要将数组的每个元素与第一个元素(标题)进行比较,如果匹配则将其删除 - 因为它在整个文件中重复出现。这似乎是剧本中真正陷入困境的地方。使用您的方法,我如何在不将 gC 分配给变量的情况下做到这一点?
  • 是否需要在输出文件的开头包含一份标头?
  • 我更新了脚本。它应该从文件中获取标题行,剪掉前导和尾随|,并在循环开始之前使用该标题启动一个新的$outfile。使用 [regex]::escape,它创建了一个新的正则表达式,它与标题行进行文字匹配,然后添加到 -match -replace 链中的 -notmatch 应该将它们丢弃在循环内。
  • @mjolinor,这是分段工作的——标题部分本身和正文部分(没有 -Append),但是当你一起运行它们时,会在它们之间插入 nul 字符或空格文件中的每个字符(在正文中)。与“-append”的工作方式有关吗?
【解决方案2】:

在处理非常大的文件时,Get-Content cmdlet 的性能不如 StreamReader。您可以像这样使用 StreamReader 逐行读取文件:

$path = 'C:\A-Very-Large-File.txt'
$r = [IO.File]::OpenText($path)
while ($r.Peek() -ge 0) {
    $line = $r.ReadLine()
    # Process $line here...
}
$r.Dispose()

一些性能比较:

Measure-Command {Get-Content .\512MB.txt > $null}

总秒数:49.4742533

Measure-Command {
    $r = [IO.File]::OpenText('512MB.txt')
    while ($r.Peek() -ge 0) {
        $r.ReadLine() > $null
    }
    $r.Dispose()
}

总秒数:27.666803

【讨论】:

  • 这是一个很好的建议。我还应该指出,脚本的 get-content 部分运行得非常快。它是我循环遍历数组中所有内容的部分,将每个元素与数组中的第一个元素进行比较,这似乎使它运行得非常慢。
  • Get-Content 确实将整个文件加载到内存中,但是将结果分配给变量。这是 PowerShell 中的一个重要设计原则 - 即使对于非常大的数据集,通过管道进行流式传输也可以节省内存。
  • 如果比较令人窒息,我想知道 PC 是否必须在检查元素 x 和元素 0 时分页输入/输出数据,因为它无法容纳内存中的整个字符串数组 - - 如果是这样,$content[0] 可能不是一个恒定时间的操作,即使在一个小数据集上它可以被假定是。也许尝试将 $content[0] 的内容复制到一个新变量中,并与该变量而不是数组元素进行比较。
  • @JayBazuzi 公平点。通常将变量分配给 Get-Content 的输出。我根据您的输入更新了我的答案。谢谢。
  • @danielrichnak,感谢您的评论 - 我没有考虑到元素 0。
【解决方案3】:

这几乎是一个不可回答的问题...我喜欢 PowerShell...但我不会使用它来解析日志文件,尤其是大型日志文件。使用微软的Log Parser

C:\>type input.txt | logparser "select substr(field1,1) from STDIN" -i:TSV -nskiplines:14 -headerrow:off -iseparator:spaces -o:tsv -headers:off -stats:off

【讨论】:

  • 这是一个很好的非答案。 Powershell 在处理大文件时可能会非常缓慢。来自 linux 背景,您希望通过 grep 相当有效地传输内容,但 Get-Content 和 Select-String 只是不竞争。我也喜欢 PowerShell,但在解析和过滤大量日志文件时不喜欢。
猜你喜欢
  • 2022-01-06
  • 2012-07-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-11-06
  • 2015-02-14
相关资源
最近更新 更多