通过Powershell拆分9 GB csv文件时出现问题

jbose2ul  于 2023-02-14  发布在  Shell
关注(0)|答案(4)|浏览(119)

我有一个大的csv文件,大约9 GB,在Powershell中,我需要将它拆分成10 MB的块,问题是我需要保持行的完整性,所以每个拆分的文件都在一行的末尾结束,并在下一行的开头开始。
由于文件太大,我需要一种方法来拆分它,而不是强制代码一次上传整个文档(否则会崩溃)。
我已经做了两个星期了,还没有找到一个可行的解决方案。最糟糕的是,代码要花一天多的时间才能达到它的内存断点,所以我只能每周测试、调试和重启代码几次。进展非常缓慢。
毫无疑问,我尝试过的每个代码都会触发一个与内存相关的错误,例如“Get-Content:内存不足,无法继续执行程序。”
我试过以下方法。
如果您有这些方法的工作版本的Powershell相关链接,或者您自己知道一个更好的方法,请参与进来。
我不会给你们完整的三个代码的副本,因为那会很长,但至少这样你们会知道我已经试过什么方法了,还有不断出现的内存不足的错误。
任何帮助都将不胜感激。如果我继续在这个问题上拔我的头发,我会在我的时间之前秃头!
以前的尝试(每次都运行了一天以上,然后由于内存错误而失败)

  1. Get-Content(我的印象是Get-Content不会尝试一次加载所有数据,因此是最佳选择。)
Get-Content $sourceAddress |
    Foreach-Object{

       $var1, var2, var3 ... = $_ -Split "," 
       
       # Push these variables into a new line in a csv file
       # If you've reached 100,000 lines, export a split file

}
  1. Import-Csv(在这里,我尝试提取一系列行之间的数据)
for ($i; $i -le $MaxSplits; $i++)
{
   
(Import-Csv $InCsv)[$Min..$Max] | 
Export-Csv -Path $outfile -NoTypeInformation

}

1.流阅读器(ChatGPT说这是最好的方法。是的,我绝望到向AI寻求编码建议)

$reader = New-Object System.IO.StreamReader($filePath)

while (!$reader.EndOfStream) {

    # Loop to retrieve the specified number of lines
    for ($i = 0; $i -lt $linesPerFile; $i++) {

        # Read the next line from the file
        # Check if the end of the file has been reached
        # If not, Add the line to the lines array
    }

    # Write the current batch of lines to the output file
    # Increment the file counter
}

同样,任何帮助都将不胜感激。

bvuwiixz

bvuwiixz1#

你可以使用steppable pipeline,因为这样可以防止你在每次迭代中打开和关闭目标文件(使用Export-Csv),这显然是非常昂贵的:

$FileSize = 10Mb
Import-Csv .\Input.csv |
    ForEach-Object -Begin {
        $Index = 0
        $Path = $Null
    } -Process {
        if (!$Path) {
            $Index++
            $Path = ".\Batch$Index.csv"
            $Pipeline = { Export-Csv -notype -Path $Path }.GetSteppablePipeline()
            $Pipeline.Begin($True)            
        }
        $Pipeline.Process($_)
        if ((Get-Item $Path).Length -gt $FileSize) {
            $Pipeline.End()
            $Path = $Null
        }
    } -End {
        $Pipeline.End()
    }

有关完整说明,请参见:Mastering the (steppable) pipeline

laawzig2

laawzig22#

我不介意多个人关注这段代码。我想我已经想到了所有的东西,但是不确定。这是在PS5.1中完成的,不确定它在PS7.x中如何工作。
这个函数用于保存每个数据块。决定将$FileLines作为参数传递而不是通过管道传递可能是个错误-我不喜欢使用[AllowEmptyString()]。我缺乏将字符串数组传递给函数的经验。
此任务的一个问题是确定源文件的编码,我找到了this,但没有在代码中使用它。我建议检查源文件以确定它使用的编码,然后在函数中设置Get-ContentOut-File命令中的一个或两个的编码以匹配它。此外,我使用了一个名为HXD的十六进制文件查看器来检查输入和输出文件。

function SaveFile {
    param (
        [Parameter(Mandatory = $true, Position = 0)]
        [int]$FileIndex,
        [Parameter(Mandatory = $true, Position = 1)]
        [AllowEmptyString()][string[]]$FileLines
    )
    $FilePath = Join-Path -Path $PSScriptRoot -ChildPath ("Chunk{0:d4}.CSV" -f $FileIndex)
    # Encoding options: ASCII, BigEndianUnicode, Default, OEM, String, Unicode, Unknown, UTF7, UTF8, UTF32
    $FileLines | Out-File -FilePath $FilePath -Encoding ascii
}

我使用的文件是我的计算机上的一些随机文件,替换为您的文件的路径和名称。
Get-Content通过管道将文件的行传输到Select-Object,然后Select-Object创建SizeText属性,这些属性是$_的成员。计算字符串大小的想法来自this answer-因为ASCII是每个字符一个字节,所以应该忠实地工作。每行的大小增加2,假设每行的结尾是CR+LF。
然后,Foreach-Object拆分为开始代码块、处理代码块和结束代码块。
在开始代码块中,注解掉当前活动的$OutFileSize赋值语句,并取消注解上面的行,应该会将输出文件大小设置为10 MB。
进程块计算当前总大小,如果大于$OutFileSize,则将当前存储的行转储到下一个文件中,并将当前总大小设置为等于当前行的大小。
结束块在退出之前保存$LineList缓冲区中的最后一个文件块。

Get-Content 'C:\WINDOWS\system32\VmChipset Third-Party Notices.txt' |
    Select-Object -Property @{Name = 'Size'; Expression = {[System.Text.Encoding]::ASCII.GetByteCount($_)+2}},@{Name = 'Text'; Expression = {$_}} |
    Foreach-Object -Begin {
        #[int]$OutFileSize = 10*1024*1024       # 10 MB size
        [int]$OutFileSize = 1024
        [int]$Index = 0
        $CurrentTotal = 0
        $LineList = [System.Collections.ArrayList]@()
    } -Process {
        if(($CurrentTotal += $_.Size) -ge $OutFileSize) {
            if($LineList.Count -gt 0) {
                SaveFile $Index $LineList.ToArray()
                $LineList.Clear()
            }
            $CurrentTotal = $_.Size
            $Index += 1
        }
        $null = $LineList.Add($_.Text)
    } -End {
        SaveFile $Index $LineList.ToArray()
    }

我在实验中使用的文件有6,658个字节:

6,658 VmChipset Third-Party Notices.txt

结果文件的大小之和也是6658。

02/07/2023  11:16 PM               967 Chunk0000.CSV
02/07/2023  11:16 PM             1,020 Chunk0001.CSV
02/07/2023  11:16 PM               990 Chunk0002.CSV
02/07/2023  11:16 PM               974 Chunk0003.CSV
02/07/2023  11:16 PM               965 Chunk0004.CSV
02/07/2023  11:16 PM               980 Chunk0005.CSV
02/07/2023  11:16 PM               762 Chunk0006.CSV

**注意:**我不期望它很快,但是如果您只需要运行一次-它可能对您有用。

jaxagkaj

jaxagkaj3#

试试这个

$reader = New-Object System.IO.StreamReader($filePath + ".csv")
$writer = $null
$lineCount = 0;
$fileCount = 1;
While(($line = $reader.ReadLine()) -ne $null)
{
   $line = $line.Trim()
   if($line.Length -gt 0)
   {
      if($lineCount % 10000000 -eq 0)
      {
         if($writer -ne $null)
         {
             $writer.Flush()
             $writer.Close()
         }      
         $writer = New-Object System.IO.StreamWriter($filePath + $fileCount + ".csv");
         $fileCount++;
      }+
   }
   $writer.WriteLine($line)
   $lineCount++;
}
$writer.Flush();
$writer.Close();
$reader.Close();
wnvonmuf

wnvonmuf4#

尝试将一个100MB的文件拆分成10个10MB的文件,占用大约0.5G的内存,耗时9秒。但9G可能需要14分钟。(我真实的的用了12分钟。)

$content = foreach ($i in 1..11mb) { 'abcdefg' }
set-content -path file.csv -value $content
get-content file.csv -ReadCount 1mb | 
  % { $i = 1 } { set-content -path file$i.csv -value $_; $i++ }

history | select -last 1 | fl

Id                 : 348
CommandLine        : get-content file.csv -ReadCount 1mb | 
  % { $i = 1 } { set-content -path file$i.csv -value $_; $i++ }
ExecutionStatus    : Completed
StartExecutionTime : 2/10/2023 11:11:38 AM
EndExecutionTime   : 2/10/2023 11:11:47 AM

dir

    Directory: C:\Users\admin\foo

Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
-a----         2/10/2023  10:56 AM      103809024 file.csv
-a----         2/10/2023  11:02 AM        9437184 file1.csv
-a----         2/10/2023  11:03 AM        9437184 file10.csv
-a----         2/10/2023  11:03 AM        9437184 file11.csv
-a----         2/10/2023  11:02 AM        9437184 file2.csv
-a----         2/10/2023  11:02 AM        9437184 file3.csv
-a----         2/10/2023  11:02 AM        9437184 file4.csv
-a----         2/10/2023  11:02 AM        9437184 file5.csv
-a----         2/10/2023  11:03 AM        9437184 file6.csv
-a----         2/10/2023  11:03 AM        9437184 file7.csv
-a----         2/10/2023  11:03 AM        9437184 file8.csv
-a----         2/10/2023  11:03 AM        9437184 file9.csv

制作一个真实的的9gb测试文件(7分钟):

foreach ($i in 1..90) { $content = foreach ($i in 1..11mb) { 'abcdefg' }
  add-content -path file.csv -value $content }

相关问题