在PowerShell中解析数​​千个小文件的最快方法

时间:2013-11-19 21:25:01

标签: powershell

我在网络共享上有超过16000个库存日志文件,大小从3-5 KB不等。 示例文件如下所示:

## System Info
SystemManufacturer:=:Dell Inc.                
SystemModel:=:OptiPlex GX620               
SystemType:=:X86-based PC
ChassisType:=:6 (Mini Tower)

## System Type
isLaptop=No

我需要将它们放入数据库中,所以我开始解析它们并为每个创建一个自定义对象,以后我可以用来检查重复项,规范化等...

使用下面的代码片段进行初始解析需要大约7.5分钟。

Foreach ($invlog in $invlogs) {
    $content = gc $invlog.FullName -ReadCount 0
    foreach ($line in $content) {
        if ($line -match '^#|^\s*$') { continue }
        $invitem,$value=$line -split ':=:'
        [PSCustomObject]@{Name=$invitem;Value=$value}
    }
}

我开始对它进行优化,经过几次试验和错误后,这需要2分钟和4秒:

 Foreach ($invlog in $invlogs) {
        foreach ($line in ([System.IO.File]::ReadLines("$($invlog.FullName)") -match '^\w')  ) {
           $invitem,$value=$line -split ':=:'
           [PSCustomObject]@{name=$invitem;Value=$value}  #2.04mins
        }
    }

我也尝试使用哈希而不是PSCustomObject,但令我惊讶的是花了更长的时间(5分钟26秒)

       Foreach ($invlog in $invlogs) {                        
        $hash=@{}        
        foreach ($line in ([System.IO.File]::ReadLines("$($invlog.FullName)") -match $propertyline)  ) {

           $invitem,$value=$line -split ':=:'
           $hash[$invitem]=$value #5.26mins
        }
    }

在这里使用最快的方法是什么?

3 个答案:

答案 0 :(得分:3)

看看这是否更快:

Foreach ($invlog in $invlogs) {
@(gc $invlog.FullName -ReadCount 0) -notmatch '^#|^\s*$' |
 foreach {
          $invitem,$value=$line -split ':=:'
          [PSCustomObject]@{Name=$invitem;Value=$value}
         }
}

-match和-notmatch运算符在应用于数组时返回满足匹配的所有元素,因此您可以消除必须测试要排除的行的每一行。

您真的想为每一行创建一个PS对象,还是只想为每个文件创建一个PS对象?

如果您想为每个文件添加一个对象,请查看是否更快: 多行正则表达式消除了行数组,并使用过滤器代替foreach来创建散列条目。

 $regex = [regex]'(?ms)^(\w+):=:([^\r]+)'
 filter make-hash { @{$_.groups[1].value = $_.groups[2].value} }

Foreach ($invlog in $invlogs) {
$regex.matches([io.file]::ReadAllText($invlog.fullname)) | make-hash
 }

切换到使用多行正则表达式和[io.file] :: ReadAllText]的目的是简化Powershell在内部对文件输入所做的事情。 [io.file] :: ReadAllText()的结果将是一个字符串对象,它是一个比[io.file] :: ReadAllLines()将生成的字符串数组更简单的对象类型,并且需要更少的开销在内部进行。过滤器本质上只是一个函数的Process块 - 它将为从管道到达它的每个对象运行一次,因此它模拟foreach-object的动作,但实际运行速度稍快(我不知道内部足以告诉你究竟为什么)。这两种变化都需要更多编码,只会导致性能略有提高。在我的测试中,切换到多行每个文件大约增加.1ms,并从foreach-object更改为过滤器另一个.1 ms。您可能不会经常使用这些技术,因为与所需的额外编码工作相比回报较低,但是当您开始将这些ms的分数乘以160K迭代时,它会变得非常重要。

答案 1 :(得分:1)

试试这个:

Foreach ($invlog in $invlogs) {
    $output = @{}
    foreach ($line in ([IO.File]::ReadLines("$($invlog.FullName)") -ne '')  ) {
        if ($line.Contains(":=:")) {
            $item, $value = $line.Split(":=:") -ne '' 
            $output[$item] = $value
        }        

    }

    New-Object PSObject -Property $output
}

作为一般规则,正则表达式有时很酷但总是较慢。

答案 2 :(得分:0)

您不希望每个系统都有一个对象,而不是每个键值对吗? :S 像这样..通过将Get-Content替换为.Net方法,您可以节省一些时间。

Get-ChildItem -Filter *.txt -Path <path to files> | ForEach-Object {

    $ht = @{}

    Get-Content $_ | Where-Object { $_ -match ':=:' } | ForEach-Object {

        $ht[($_ -split ':=:')[0].Trim()] = ($_ -split ':=:')[1].Trim()

    }

    [pscustomobject]$ht

}

ChassisType                          SystemManufacturer                   SystemType                          SystemModel
-----------                          ------------------                   ----------                          -----------
6 (Mini Tower)                       Dell Inc.                            X86-based PC                        OptiPlex GX620