gpt4 book ai didi

powershell - 使用Powershell在大型CSV文件中更改定界符

转载 作者:行者123 更新时间:2023-12-02 23:41:54 25 4
gpt4 key购买 nike

我需要一种将CSV文件中的定界符从逗号更改为管道的方法。由于CSV文件的大小(大约750 Mb到几Gb),因此不能选择使用Import-CSV和/或Get-Content。我正在使用的代码(虽然很慢,但仍然有效)是以下代码:

$reader = New-Object Microsoft.VisualBasic.FileIO.TextFieldParser $source
$reader.SetDelimiters(",")

While(!$reader.EndOfData)
{
$line = $reader.ReadFields()
$details = [ordered]@{
"Plugin ID" = $line[0]
CVE = $line[1]
CVSS = $line[2]
Risk = $line[3]
}
$export = New-Object PSObject -Property $details
$export | Export-Csv -Append -Delimiter "|" -Force -NoTypeInformation -Path "C:\MyFolder\Delimiter Change.csv"
}

这个小循环花了将近2分钟的时间来处理20 Mb的文件。以这种速度放大将意味着一个小时以上的时间来处理我目前正在使用的最小CSV文件。

我也尝试过:
While(!$reader.EndOfData)
{
$line = $reader.ReadFields()

$details = [ordered]@{
# Same data as before
}

$export.Add($details) | Out-Null
}

$export | Export-Csv -Append -Delimiter "|" -Force -NoTypeInformation -Path "C:\MyFolder\Delimiter Change.csv"

这非常快,但是在新的CSV中未提供正确的信息。相反,我得到这样的行:
"Count"|"IsReadOnly"|"Keys"|"Values"|"IsFixedSize"|"SyncRoot"|"IsSynchronized"
"13"|"False"|"System.Collections.Specialized.OrderedDictionary+OrderedDictionaryKeyValueCollection"|"System.Collections.Specialized.OrderedDictionary+OrderedDictionaryKeyValueCollection"|"False"|"System.Object"|"False"
"13"|"False"|"System.Collections.Specialized.OrderedDictionary+OrderedDictionaryKeyValueCollection"|"System.Collections.Specialized.OrderedDictionary+OrderedDictionaryKeyValueCollection"|"False"|"System.Object"|"False"

因此,有两个问题:

1)可以使第一段代码更快吗?
2)如何在第二个示例中解开arraylist以获得实际数据?

编辑:在这里找到示例数据- http://pastebin.com/6L98jGNg

最佳答案

这是简单的文本处理,因此瓶颈应该是磁盘读取速度:
OP的样本(重复到上述大小)每100 MB 1秒或每1GB 10秒,如在i7上测量的。对于带有很多/全部小的带引号的字段的文件,结果将更糟。

该算法很简单:

  • 以大字符串块形式读取文件,例如1MB。
    它比读取用CR / LF分隔的数百万行快得多,因为:
  • 较少执行检查,因为我们主要/主要是只寻找双引号;
  • 较少由解释器执行的代码迭代,这很慢。
  • 查找下一个双引号。
  • 取决于当前的$inQuotedField标志,确定找到的双引号是开始一个带引号的字段(应在,之前加上一些空格),还是在当前被引用的字段结束(应在后面加上偶数个双引号,可选的空格和,) 。
  • 如果找不到引号,请在前面的跨度或1MB块末尾替换定界符。

  • 该代码做出了一些合理的假设,但是如果在字段定界符之前/之后的双引号后面或前面有超过三个空格,则可能无法检测到转义的字段。添加支票并不太难,我可能已经错过了其他一些优势,但我对此并不感兴趣。
    $sourcePath = 'c:\path\file.csv'
    $targetPath = 'd:\path\file2.csv'
    $targetEncoding = [Text.UTF8Encoding]::new($false) # no BOM

    $delim = [char]','
    $newDelim = [char]'|'

    $buf = [char[]]::new(1MB)
    $sourceBase = [IO.FileStream]::new(
    $sourcePath,
    [IO.FileMode]::open,
    [IO.FileAccess]::read,
    [IO.FileShare]::read,
    $buf.length, # let OS prefetch the next chunk in background
    [IO.FileOptions]::SequentialScan)
    $source = [IO.StreamReader]::new($sourceBase, $true) # autodetect encoding
    $target = [IO.StreamWriter]::new($targetPath, $false, $targetEncoding, $buf.length)

    $bufStart = 0
    $bufPadding = 4
    $inQuotedField = $false
    $fieldBreak = [char[]]@($delim, "`r", "`n")
    $out = [Text.StringBuilder]::new($buf.length)

    while ($nRead = $source.Read($buf, $bufStart, $buf.length-$bufStart)) {
    $s = [string]::new($buf, 0, $nRead+$bufStart)
    $len = $s.length
    $pos = 0
    $out.Clear() >$null

    do {
    $iQuote = $s.IndexOf([char]'"', $pos)
    if ($inQuotedField) {
    $iDelim = if ($iQuote -ge 0) { $s.IndexOf($delim, $iQuote+1) }
    if ($iDelim -eq -1 -or $iQuote -le 0 -or $iQuote -ge $len - $bufPadding) {
    # no closing quote in buffer safezone
    $out.Append($s.Substring($pos, $len-$bufPadding-$pos)) >$null
    break
    }
    if ($s.Substring($iQuote, $iDelim-$iQuote+1) -match "^(""+)\s*$delim`$") {
    # even number of quotes are just quoted quotes
    $inQuotedField = $matches[1].length % 2 -eq 0
    }
    $out.Append($s.Substring($pos, $iDelim-$pos+1)) >$null
    $pos = $iDelim + 1
    continue
    }
    if ($iQuote -ge 0) {
    $iDelim = $s.LastIndexOfAny($fieldBreak, $iQuote)
    if (!$s.Substring($iDelim+1, $iQuote-$iDelim-1).Trim()) {
    $inQuotedField = $true
    }
    $replaced = $s.Substring($pos, $iQuote-$pos+1).Replace($delim, $newDelim)
    } elseif ($pos -gt 0) {
    $replaced = $s.Substring($pos).Replace($delim, $newDelim)
    } else {
    $replaced = $s.Replace($delim, $newDelim)
    }
    $out.Append($replaced) >$null
    $pos = $iQuote + 1
    } while ($iQuote -ge 0)

    $target.Write($out)

    $bufStart = 0
    for ($i = $out.length; $i -lt $s.length; $i++) {
    $buf[$bufStart++] = $buf[$i]
    }
    }
    if ($bufStart) { $target.Write($buf, 0, $bufStart) }
    $source.Close()
    $target.Close()

    关于powershell - 使用Powershell在大型CSV文件中更改定界符,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/39536649/

    25 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com