【问题标题】:Split CSV with powershell使用 powershell 拆分 CSV
【发布时间】:2013-02-07 15:05:16
【问题描述】:

我有大型 CSV 文件(每个 50-500 MB)。在这些上运行复杂的 power shell 命令需要很长时间和/或遇到内存问题。

处理数据需要按常用字段分组,例如在 ColumnA 中。因此,假设数据已经按该列排序,如果我随机拆分这些文件(即每 x 千行),那么匹配的条目仍然可能最终出现在不同的部分。 A 中有数千个不同的组,因此将每个组拆分为一个文件会创建多个文件。

如何将其拆分为 10,000 行左右的文件而不丢失组?例如。第 1-13 行将是 A 列中的 A1,第 14-17 行将是 A2 等,第 9997-10012 行将是 A784。在这种情况下,我希望第一个文件包含第 1-10012 行,下一个文件从第 10013 行开始。

显然我想保留整行(而不仅仅是 A 列),所以如果我将所有结果文件粘贴在一起,这将与原始文件相同。

【问题讨论】:

  • 如果它更容易实现,那么我不介意 10,000 是最大值而不是最小值。所以在上面的例子中,第一个文件将是第 1-9996 行。

标签: powershell csv


【解决方案1】:

未测试。这假设 ColumnA 是第一列,它是常见的逗号分隔数据。您需要调整创建正则表达式的行以适合您的数据。

 $count = 0

 $header = get-content file.csv -TotalCount 1

 get-content file.csv -ReadCount 1000 |
  foreach {
   #add tail entries from last batch to beginning of this batch
   $newbatch = $tail + $_ 

   #create regex to match last entry in this batch
   $regex = '^' + [regex]::Escape(($newbatch[-1].split(',')[0])) 

   #Extract everything that doesn't match the last entry to new file

     #Add header if this is not the first file
     if ($count)
       {
         $header |
           set-content "c:\somedir\filepart_$count"
        }

     $newbatch -notmatch $regex | 
      add-content "c:\somedir\filepart_$count"  

   #Extact tail entries to add to next batch
   $tail = @($newbatch -match $regex)

   #Increment file counter
   $count++ 

}

【讨论】:

  • 非常好。但是,您应该在所有部分中包含 csvheader,至少我会希望这样做。
  • 同意,现在可以很好地与标题一起使用。您可以添加(管道吗?)用于按 ColumnA 对输入进行排序,它实际上是文件的第二列。已将 [0] 调整为 [1] 有效。
  • 问题是假设数据已经按该列排序......
  • 谢谢! (“我在偷那个!”是对编剧的高度赞扬)
  • 太棒了!让我感到奇怪的是:有分号分隔的数据,我预计我必须将其调整为 $regex = '^' + [regex]::Escape(($newbatch[-1].split(';')[0]))。但是,乍一看,我忘记了,它与逗号配合得很好......让我想知道:在这种情况下,正则表达式到底做了什么?
【解决方案2】:

这是我的尝试,它变得混乱 :-P 它会在拆分文件时将整个文件加载到内存中,但这是纯文本。它应该比导入对象占用更少的内存,但仍然与文件大小差不多。

$filepath = "C:\Users\graimer\Desktop\file.csv"
$file = Get-Item $filepath
$content = Get-Content $file
$csvheader = $content[0]
$lines = $content.Count
$minlines = 10000
$filepart = 1

$start = 1

while ($start -lt $lines - 1) {
    #Set minimum $end value (last line)
    if ($start + $minlines -le $lines - 1) { $end = $start + $minlines - 1 } else { $end = $lines - 1 }

    #Value to compare. ColA is first column in my file = [0] .  ColB is second column = [1]
    $avalue = $content[$end].split(",")[0]
    #If not last line in script
    if ($end -ne $lines -1) {
        #Increase $end by 1 while ColA is the same
        while ($content[$end].split(",")[0] -eq $avalue) { $end++ }
        #Return to last line with equal ColA value
        $end--
    }
    #Create new csv-part
    $filename = $file.FullName.Replace($file.BaseName, ($file.BaseName + ".part$filepart"))
    @($csvheader, $content[$start..$end]) | Set-Content $filename

    #Fix counters
    $filepart++
    $start = $end + 1
}

文件.csv:

ColA,ColB,ColC
A1,1,10
A1,2,20
A1,3,30
A2,1,10
A2,2,20
A3,1,10
A4,1,10
A4,2,20
A4,3,30
A4,4,40
A4,5,50
A4,6,60
A5,1,10
A6,1,10
A7,1,10

结果(我用$minlines = 5):

file.part1.csv:

ColA,ColB,ColC
A1,1,10
A1,2,20
A1,3,30
A2,1,10
A2,2,20

file.part2.csv:

ColA,ColB,ColC
A3,1,10
A4,1,10
A4,2,20
A4,3,30
A4,4,40
A4,5,50
A4,6,60

file.part3.csv:

ColA,ColB,ColC
A5,1,10
A6,1,10
A7,1,10

【讨论】:

  • 看起来非常接近,但生成的文件是原始文件的两倍,并且似乎与原始文件不是相同的“CSV”(在 Excel 中打开时,它不会拾取列) ,虽然在记事本中看起来不错。另外,您能否修改输入,使其按 ColumnA 排序。请注意,这实际上不是文件的第一列。
  • 固定文件大小。您必须替换分隔符和列号以满足您的需要。在我的示例中,它们是“,”和 0(第一个)(使用 split() 查看这两行并替换值)。你说文件已经排序了。如果要对 THIS 脚本中的列进行排序,最好的方法是从 csv 导入对象。这将需要更多内存,这首先是您的问题
【解决方案3】:

这需要 PowerShell v3(由于 -append on Export-CSV)。

另外,我假设您有列标题并且第一列名为col1。根据需要进行调整。

import-csv MYFILE.csv|foreach-object{$_|export-csv -notypeinfo -noclobber -append ($_.col1 + ".csv")}

这将为第一列中的每个不同值创建一个文件,并将该值作为文件名。

【讨论】:

  • 谢谢,但如上所述,这会创建太多文件
  • 我不清楚数据是如何设置的。您是说您希望每个文件有多个组,而不是将单个组拆分为多个文件?
  • 我希望每个文件有多个组,但没有文件超过 10,000 行。即一旦达到 10,000,继续使用该组,然后停止并开始下一个组的下一个文件
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2021-05-30
  • 1970-01-01
  • 2014-03-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多