【问题标题】:Filter large CSV files过滤大型 CSV 文件
【发布时间】:2019-11-02 01:29:14
【问题描述】:

我有一些具有相同标题的大型 CSV 文件 (500MB+),我想在过滤机器类型 = 工作站后将其合并到一个合并文件中。如何将特定列标题“machine_type”过滤为“工作站”。下面的代码可以工作,但会创建包含太多行数据的 CSV 文件。感谢帮助。 Import-Csv 给了我“System.OutofMemoryException”异常。

$inputFolder = c:\change\imput
$outputFile  = 'C:\Change\filtered.csv'

$writer = New-Object IO.StreamWriter ($outputFile, $false)

Get-ChildItem $inputFolder -File | Where-Object {
$_.Extension -eq '.csv'
} | ForEach-Object {
  $reader = New-Object IO.StreamReader ($_.FullName)
  if (-not $headerWritten) {
    # copy header line to output file once
    $writer.WriteLine($reader.ReadLine())
    $headerWritten = $true
  } else {
    # discard header line
    $reader.ReadLine()
  }

  while ($reader.Peek() -ge 0) {
    $line   = $reader.ReadLine()
    $fields = $line -split ','
    #if ($line -match 'Workstation' ) {  
      $writer.WriteLine($line)
    #}
  }

  $reader.Close()
  $reader.Dispose()
}

$writer.Close()
$writer.Dispose()

【问题讨论】:

  • $line -split ',' 将创建一个数组并存储在$fields 中。只需使用与您想要的数据相对应的正确索引,例如$fields[0]$fields[2].
  • "Too many row data" => 你能举一个不应该出现在输出中的行的例子吗?
  • $line = $reader.ReadLine() $fields = $line -split ',' if ($fields[6] -match 'workstation' ) { $writer.WriteLine($line) }这可行,但 csv 文件的写入不会在每个 csv 行数据之后截断,输出列号延伸到“IVW”。我如何确保 $writer.WriteLine($line) 截断到下一行。
  • 奇怪。听起来 .ReadLine() 没有拿起行尾。如果您读取一行,并将其原封不动地写入控制台而不是文件,是否会出现同样的问题?

标签: powershell csv out-of-memory


【解决方案1】:

这应该不会导致OOM异常:

Get-ChildItem $inputFolder -File -Filter '*.csv' |
    ForEach-Object { Import-Csv $_.FullName } |
    Where-Object { $_.machine_type -eq 'workstations' } |
    Export-Csv $outputFile -NoType

【讨论】:

  • 谢谢安斯加尔。但仍然得到“抛出'System.OutOfMemoryException'类型的异常.. csv 文件的总大小约为 1.8 GB。
  • @Enigma 指的是这个devblogs.microsoft.com/scripting/…
  • @Matthew 管道应该一次处理一个输入项,因此除非您有一个积累数据的管道步骤,否则根本不会发生内存耗尽。不过,我现在无法测试。今晚我回家看看。
  • @AnsgarWiechers Import-Csv 在括号之间。会不会是数据在那里积累,而不是沿着管道走下去?可能是这样(现在无法测试):Get-ChildItem $inputFolder -File -Filter '*.csv' | ForEach-Object { Import-Csv $_.FullName | Where-Object { $_.machine_type -eq 'workstations' } | Export-Csv $outputFile -NoType }
  • @BertVanLandeghem AFAIK 在ForEach-Object 中的输出应该直接返回到管道中。数据由foreach 循环和Sort-Object 等cmdlet 累积,它们需要比较它们处理的项目。
【解决方案2】:

如果我错了,请纠正我,但我猜“工作站”这个词也出现在工作站类型以外的其他列中,所以它也匹配那些行?您可以为此使用更精细的正则表达式(使用捕获组),请参见下面的代码:

$inputFolder = 'c:\temp\csv'
$outputFile  = 'C:\temp\filtered.csv'

$headerWritten = $false
[regex] $csvPattern = '(?imx)^(?<name>\w+)(\s?,\s?)(?<machine_type>\w+)(\s?,\s?)(?<location>\w+)'

$writer = New-Object IO.StreamWriter ($outputFile, $false)

Get-ChildItem $inputFolder -File | Where-Object {
$_.Extension -eq '.csv'
} | ForEach-Object {
  $reader = New-Object IO.StreamReader ($_.FullName)
  if (-not $headerWritten) {
    # copy header line to output file once
    $writer.WriteLine($reader.ReadLine())
    $headerWritten = $true
  } else {
    # discard header line
    $reader.ReadLine()
  }

  while ($reader.Peek() -ge 0) {
    $line   = $reader.ReadLine()

    if ($csvPattern.Match($line).Groups.Item('machine_type').value -eq 'WorkStation' ) {  
        if ($line -match 'Workstation' ){
      $writer.WriteLine($line)
    }
  }

  $reader.Close()
  $reader.Dispose()
}

$writer.Close()
$writer.Dispose()

【讨论】:

  • 嗨 Bert.. “工作站”类型仅在 Machine_type 列中可用。剩下的将是机器规格和其他细节。我已经运行了上面的代码。不知何故没有得到任何结果。你能确认 " [regex] $csvPattern = '(?imx)^(?\w+)(\s?,\s?)(?\w+)(\s?,\s?) (?\w+)'" 它会检查所有列数据上的“工作站”吗??
  • 正则表达式是为具有 3 列的 csv 构建的,由 ',' 分隔并删除空格。其中,有 3 个命名捕获组“名称”、“机器类型”和“位置”。您必须调整正则表达式以对应于导入文件中的列。在 If 中,代码检查捕获组“machine_type”的值是否为“workstation”。针对一些示例数据构建和验证正则表达式的最简单方法是使用在线可视化工具。我使用regex101.com,但可能还有很多其他人。它还将向您展示命名捕获组的概念。
猜你喜欢
  • 2021-03-15
  • 2016-04-09
  • 1970-01-01
  • 2022-11-05
  • 1970-01-01
  • 2011-09-20
  • 2018-07-16
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多