PowerShell, чтобы разбить CSV по числу строк - PullRequest
0 голосов
/ 07 ноября 2018

Итак, теперь мне поручено получать постоянные отчеты длиной более 1 миллиона строк.

Мой последний вопрос не объяснял всех вещей, поэтому я попробую сделать лучший вопрос.

Я получаю дюжину + ежедневных отчетов, которые приходят в виде файлов CSV. Я не знаю, что это за заголовки или что-то в этом роде, когда я их получаю.

Они огромные. Я не могу открыть в Excel.

Я хотел в основном разбить их на один и тот же отчет, просто каждый отчет может быть длиной в 100 000 строк.

Код, который я написал ниже, не работает, так как я продолжаю получать

Exception of type 'System.OutOfMemoryException' was thrown.

Полагаю, мне нужен лучший способ сделать это.

Мне просто нужно, чтобы этот файл был разбит на более управляемый размер. Неважно, сколько времени это займет, так как я могу управлять им всю ночь.

Я нашел это в интернете и пытался им манипулировать, но не могу заставить его работать.

$PSScriptRoot

write-host $PSScriptRoot

$loc = $PSScriptRoot

$location = $loc

# how many rows per CSV?
$rowsMax = 10000; 

# Get all CSV under current folder
$allCSVs = Get-ChildItem "$location\Split.csv"


# Read and split all of them
$allCSVs | ForEach-Object {
    Write-Host $_.Name;
    $content = Import-Csv "$location\Split.csv"
    $insertLocation = ($_.Name.Length - 4);
    for($i=1; $i -le $content.length ;$i+=$rowsMax){
    $newName = $_.Name.Insert($insertLocation, "splitted_"+$i)
    $content|select -first $i|select -last $rowsMax | convertto-csv -NoTypeInformation | % { $_ -replace '"', ""} | out-file $location\$newName -fo -en ascii
    }
}

Ответы [ 2 ]

0 голосов
/ 07 ноября 2018

Еще один вариант из мира Linux - команда split. Чтобы установить его на Windows, просто установите git bash, и тогда вы сможете использовать множество инструментов linux в вашей CMD / powershell. Ниже приведен синтаксис для достижения вашей цели:

split  -l 100000 --numeric-suffixes --suffix-length 3 --additional-suffix=.csv sourceFile.csv outputfile

Это очень быстро. Если вы хотите, вы можете обернуть split.exe как командлет

0 голосов
/ 07 ноября 2018

Ключ не в том, чтобы читать большие файлы в память полностью , что вы и делаете, фиксируя вывод из Import-Csv в переменную ($content = Import-Csv "$location\Split.csv").

Тем не менее, хотя с использованием одного конвейера решит вашу проблему с памятью, производительность, скорее всего, будет низкой , потому что вы конвертируете из CSV и обратно в него, что влечет за собой много накладных расходов.

Даже чтение и запись файлов в виде текста с Get-Content и Set-Content идет медленно, однако.
Поэтому я предлагаю подход на основе .NET для обработки файлов в виде текста , который должен существенно ускорить обработку.

Следующий код демонстрирует эту технику:

Get-ChildItem $PSScriptRoot/*.csv | ForEach-Object {

    $csvFile = $_.FullName

    # Construct a file-path template for the sequentially numbered chunk
    # files; e.g., "...\file_split_001.csv"
    $csvFileChunkTemplate = $csvFile -replace '(.+)\.(.+)', '$1_split_{0:000}.$2'

    # Set how many lines make up a chunk.
    $chunkLineCount = 10000

    # Read the file lazily and save every chunk of $chunkLineCount
    # lines to a new file.
    $i = 0; $chunkNdx = 0
    foreach ($line in [IO.File]::ReadLines($csvFile)) {
        if ($i -eq 0) { ++$i; $header = $line; continue } # Save header line.
        if ($i++ % $chunkLineCount -eq 1) { # Create new chunk file.
            # Close previous file, if any.
            if (++$chunkNdx -gt 1) { $fileWriter.Dispose() }

            # Construct the file path for the next chunk, by
            # instantiating the template with the next sequence number.
            $csvFileChunk = $csvFileChunkTemplate -f $chunkNdx
            Write-Verbose "Creating chunk: $csvFileChunk"

            # Create the next chunk file and write the header.
            $fileWriter = [IO.File]::CreateText($csvFileChunk)
            $fileWriter.WriteLine($header)
        }
        # Write a data row to the current chunk file.
        $fileWriter.WriteLine($line)
    }
    $fileWriter.Dispose() # Close the last file.

}

Обратите внимание, что приведенный выше код создает файлы без UTF-8 без спецификации; если ваши входные данные содержат только символы диапазона ASCII, эти файлы фактически будут файлами ASCII.


Вот эквивалентное однопроводное решение , которое, вероятно, будет значительно медленнее.

Get-ChildItem $PSScriptRoot/*.csv | ForEach-Object {

    $csvFile = $_.FullName

    # Construct a file-path template for the sequentially numbered chunk
    # files; e.g., ".../file_split_001.csv"
    $csvFileChunkTemplate = $csvFile -replace '(.+)\.(.+)', '$1_split_{0:000}.$2'

    # Set how many lines make up a chunk.
    $chunkLineCount = 10000

    $i = 0; $chunkNdx = 0
    Get-Content -LiteralPath $csvFile | ForEach-Object {
        if ($i -eq 0) { ++$i; $header = $_; return } # Save header line.
        if ($i++ % $chunkLineCount -eq 1) { # 
            # Construct the file path for the next chunk.
            $csvFileChunk = $csvFileChunkTemplate -f ++$chunkNdx
            Write-Verbose "Creating chunk: $csvFileChunk"
            # Create the next chunk file and write the header.
            Set-Content -Encoding ASCII -LiteralPath $csvFileChunk -Value $header
        }
        # Write data row to the current chunk file.
        Add-Content -Encoding ASCII -LiteralPath $csvFileChunk -Value $_
    }

}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...