Как разбить текстовый файл с помощью PowerShell?

Мне нужно разделить большой (500 МБ) текстовый файл (файл исключений log4net) на управляемые fragmentы, такие как 100 5 МБ файлов.

Я бы подумал, что это должна быть прогулка в парке для PowerShell. Как мне это сделать?

Это довольно простая задача для PowerShell, осложненная тем, что стандартный командлет Get-Content слишком плохо обрабатывает очень большие файлы. То, что я хотел бы предложить, – использовать class .NET StreamReader для чтения файла по строкам в сценарии PowerShell и использовать командлет Add-Content для записи каждой строки в файл с постоянно увеличивающимся индексом в имени файла. Что-то вроде этого:

 $upperBound = 50MB # calculated by Powershell $ext = "log" $rootName = "log_" $reader = new-object System.IO.StreamReader("C:\Exceptions.log") $count = 1 $fileName = "{0}{1}.{2}" -f ($rootName, $count, $ext) while(($line = $reader.ReadLine()) -ne $null) { Add-Content -path $fileName -value $line if((Get-ChildItem -path $fileName).Length -ge $upperBound) { ++$count $fileName = "{0}{1}.{2}" -f ($rootName, $count, $ext) } } $reader.Close() 

Слово предупреждения о некоторых из существующих ответов – они будут работать очень медленно для очень больших файлов. Для файла журнала объемом 1,6 ГБ я сдался через пару часов, понимая, что это не закончится, прежде чем я вернусь на работу на следующий день.

Два вопроса: открывается вызов Add-Content , ищет и затем закрывает текущий файл назначения для каждой строки исходного файла. Каждый раз чтение немного исходного файла и поиск новых строк также замедляет работу, но я предполагаю, что основной причиной является Add-Content.

Следующий вариант дает немного менее приятный вывод: он будет разделять файлы в середине строк, но он разбивает мой 1,6-ГБ журнал менее чем за минуту:

 $from = "C:\temp\large_log.txt" $rootName = "C:\temp\large_log_chunk" $ext = "txt" $upperBound = 100MB $fromFile = [io.file]::OpenRead($from) $buff = new-object byte[] $upperBound $count = $idx = 0 try { do { "Reading $upperBound" $count = $fromFile.Read($buff, 0, $buff.Length) if ($count -gt 0) { $to = "{0}.{1}.{2}" -f ($rootName, $idx, $ext) $toFile = [io.file]::OpenWrite($to) try { "Writing $count to $to" $tofile.Write($buff, 0, $count) } finally { $tofile.Close() } } $idx ++ } while ($count -gt 0) } finally { $fromFile.Close() } 

Простой однострочный разделитель на основе количества строк (в этом случае 100):

 $i=0; Get-Content .....log -ReadCount 100 | %{$i++; $_ | Out-File out_$i.txt} 

То же, что и все ответы здесь, но с помощью StreamReader / StreamWriter для разделения на новые строки (строка за строкой, вместо того, чтобы сразу же прочитать весь файл в памяти). Этот подход может разделить большие файлы самым быстрым способом, о котором я знаю.

Примечание. Я делаю очень мало проверки ошибок, поэтому я не могу гарантировать, что он будет работать плавно для вашего дела. Это было для моего ( 1,7 ГБ TXT-файла из 4 миллионов строк, разделенных на 100 000 строк на файл за 95 секунд ).

 #split test $sw = new-object System.Diagnostics.Stopwatch $sw.Start() $filename = "C:\Users\Vincent\Desktop\test.txt" $rootName = "C:\Users\Vincent\Desktop\result" $ext = ".txt" $linesperFile = 100000#100k $filecount = 1 $reader = $null try{ $reader = [io.file]::OpenText($filename) try{ "Creating file number $filecount" $writer = [io.file]::CreateText("{0}{1}.{2}" -f ($rootName,$filecount.ToString("000"),$ext)) $filecount++ $linecount = 0 while($reader.EndOfStream -ne $true) { "Reading $linesperFile" while( ($linecount -lt $linesperFile) -and ($reader.EndOfStream -ne $true)){ $writer.WriteLine($reader.ReadLine()); $linecount++ } if($reader.EndOfStream -ne $true) { "Closing file" $writer.Dispose(); "Creating file number $filecount" $writer = [io.file]::CreateText("{0}{1}.{2}" -f ($rootName,$filecount.ToString("000"),$ext)) $filecount++ $linecount = 0 } } } finally { $writer.Dispose(); } } finally { $reader.Dispose(); } $sw.Stop() Write-Host "Split complete in " $sw.Elapsed.TotalSeconds "seconds" 

Выходное разделение файла объемом 1,7 ГБ:

 ... Creating file number 45 Reading 100000 Closing file Creating file number 46 Reading 100000 Closing file Creating file number 47 Reading 100000 Closing file Creating file number 48 Reading 100000 Split complete in 95.6308289 seconds 

Мне часто нужно делать то же самое. Трюк получает заголовок, повторяемый в каждом из разделенных кусков. Я написал следующий командлет (PowerShell v2 CTP 3), и он делает трюк.

 ############################################################################## #.SYNOPSIS # Breaks a text file into multiple text files in a destination, where each # file contains a maximum number of lines. # #.DESCRIPTION # When working with files that have a header, it is often desirable to have # the header information repeated in all of the split files. Split-File # supports this functionality with the -rc (RepeatCount) parameter. # #.PARAMETER Path # Specifies the path to an item. Wildcards are permitted. # #.PARAMETER LiteralPath # Specifies the path to an item. Unlike Path, the value of LiteralPath is # used exactly as it is typed. No characters are interpreted as wildcards. # If the path includes escape characters, enclose it in single quotation marks. # Single quotation marks tell Windows PowerShell not to interpret any # characters as escape sequences. # #.PARAMETER Destination # (Or -d) The location in which to place the chunked output files. # #.PARAMETER Count # (Or -c) The maximum number of lines in each file. # #.PARAMETER RepeatCount # (Or -rc) Specifies the number of "header" lines from the input file that will # be repeated in each output file. Typically this is 0 or 1 but it can be any # number of lines. # #.EXAMPLE # Split-File bigfile.csv 3000 -rc 1 # #.LINK # Out-TempFile ############################################################################## function Split-File { [CmdletBinding(DefaultParameterSetName='Path')] param( [Parameter(ParameterSetName='Path', Position=1, Mandatory=$true, ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true)] [String[]]$Path, [Alias("PSPath")] [Parameter(ParameterSetName='LiteralPath', Mandatory=$true, ValueFromPipelineByPropertyName=$true)] [String[]]$LiteralPath, [Alias('c')] [Parameter(Position=2,Mandatory=$true)] [Int32]$Count, [Alias('d')] [Parameter(Position=3)] [String]$Destination='.', [Alias('rc')] [Parameter()] [Int32]$RepeatCount ) process { # yeah! the cmdlet supports wildcards if ($LiteralPath) { $ResolveArgs = @{LiteralPath=$LiteralPath} } elseif ($Path) { $ResolveArgs = @{Path=$Path} } Resolve-Path @ResolveArgs | %{ $InputName = [IO.Path]::GetFileNameWithoutExtension($_) $InputExt = [IO.Path]::GetExtension($_) if ($RepeatCount) { $Header = Get-Content $_ -TotalCount:$RepeatCount } # get the input file in manageable chunks $Part = 1 Get-Content $_ -ReadCount:$Count | %{ # make an output filename with a suffix $OutputFile = Join-Path $Destination ('{0}-{1:0000}{2}' -f ($InputName,$Part,$InputExt)) # In the first iteration the header will be # copied to the output file as usual # on subsequent iterations we have to do it if ($RepeatCount -and $Part -gt 1) { Set-Content $OutputFile $Header } # write this chunk to the output file Write-Host "Writing $OutputFile" Add-Content $OutputFile $_ $Part += 1 } } } } 

Я нашел этот вопрос, пытаясь разделить несколько контактов в одном файле VCF vCard для разделения файлов. Вот что я сделал на основе кода Ли. Мне нужно было посмотреть, как создать новый объект StreamReader и изменить значение null до $ null.

 $reader = new-object System.IO.StreamReader("C:\Contacts.vcf") $count = 1 $filename = "C:\Contacts\{0}.vcf" -f ($count) while(($line = $reader.ReadLine()) -ne $null) { Add-Content -path $fileName -value $line if($line -eq "END:VCARD") { ++$count $filename = "C:\Contacts\{0}.vcf" -f ($count) } } $reader.Close() 

Многие из этих ответов были слишком медленными для моих исходных файлов. Мои исходные файлы – это файлы SQL размером от 10 МБ до 800 МБ, которые необходимо разделить на файлы примерно равных строк.

Я нашел некоторые из предыдущих ответов, которые используют Add-Content, чтобы быть довольно медленными. Ожидание много часов для раскола до конца было не редкостью.

Я не пробовал ответить Тифлозавруса , но он видит только разрывы по размеру файла, а не по количеству строк.

Следующее мне подходит.

 $sw = new-object System.Diagnostics.Stopwatch $sw.Start() Write-Host "Reading source file..." $lines = [System.IO.File]::ReadAllLines("C:\Temp\SplitTest\source.sql") $totalLines = $lines.Length Write-Host "Total Lines :" $totalLines $skip = 0 $count = 100000; # Number of lines per file # File counter, with sort friendly name $fileNumber = 1 $fileNumberString = $filenumber.ToString("000") while ($skip -le $totalLines) { $upper = $skip + $count - 1 if ($upper -gt ($lines.Length - 1)) { $upper = $lines.Length - 1 } # Write the lines [System.IO.File]::WriteAllLines("C:\Temp\SplitTest\result$fileNumberString.txt",$lines[($skip..$upper)]) # Increment counters $skip += $count $fileNumber++ $fileNumberString = $filenumber.ToString("000") } $sw.Stop() Write-Host "Split complete in " $sw.Elapsed.TotalSeconds "seconds" 

Для файла 54 МБ я получаю вывод …

 Reading source file... Total Lines : 910030 Split complete in 1.7056578 seconds 

Я надеюсь, что другие, ищущие простой, основанный на линии сценарий разбиения, соответствующий моим требованиям, найдут это полезным.

Существует также этот быстрый (и несколько грязный) однострочный:

 $linecount=0; $i=0; Get-Content .\BIG_LOG_FILE.txt | %{ Add-Content OUT$i.log "$_"; $linecount++; if ($linecount -eq 3000) {$I++; $linecount=0 } } 

Вы можете настроить количество первых строк на каждую партию, изменив твердое значение 3000.

Я сделал небольшую модификацию для разделения файлов на основе размера каждой части.

 ############################################################################## #.SYNOPSIS # Breaks a text file into multiple text files in a destination, where each # file contains a maximum number of lines. # #.DESCRIPTION # When working with files that have a header, it is often desirable to have # the header information repeated in all of the split files. Split-File # supports this functionality with the -rc (RepeatCount) parameter. # #.PARAMETER Path # Specifies the path to an item. Wildcards are permitted. # #.PARAMETER LiteralPath # Specifies the path to an item. Unlike Path, the value of LiteralPath is # used exactly as it is typed. No characters are interpreted as wildcards. # If the path includes escape characters, enclose it in single quotation marks. # Single quotation marks tell Windows PowerShell not to interpret any # characters as escape sequences. # #.PARAMETER Destination # (Or -d) The location in which to place the chunked output files. # #.PARAMETER Size # (Or -s) The maximum size of each file. Size must be expressed in MB. # #.PARAMETER RepeatCount # (Or -rc) Specifies the number of "header" lines from the input file that will # be repeated in each output file. Typically this is 0 or 1 but it can be any # number of lines. # #.EXAMPLE # Split-File bigfile.csv -s 20 -rc 1 # #.LINK # Out-TempFile ############################################################################## function Split-File { [CmdletBinding(DefaultParameterSetName='Path')] param( [Parameter(ParameterSetName='Path', Position=1, Mandatory=$true, ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true)] [String[]]$Path, [Alias("PSPath")] [Parameter(ParameterSetName='LiteralPath', Mandatory=$true, ValueFromPipelineByPropertyName=$true)] [String[]]$LiteralPath, [Alias('s')] [Parameter(Position=2,Mandatory=$true)] [Int32]$Size, [Alias('d')] [Parameter(Position=3)] [String]$Destination='.', [Alias('rc')] [Parameter()] [Int32]$RepeatCount ) process { # yeah! the cmdlet supports wildcards if ($LiteralPath) { $ResolveArgs = @{LiteralPath=$LiteralPath} } elseif ($Path) { $ResolveArgs = @{Path=$Path} } Resolve-Path @ResolveArgs | %{ $InputName = [IO.Path]::GetFileNameWithoutExtension($_) $InputExt = [IO.Path]::GetExtension($_) if ($RepeatCount) { $Header = Get-Content $_ -TotalCount:$RepeatCount } Resolve-Path @ResolveArgs | %{ $InputName = [IO.Path]::GetFileNameWithoutExtension($_) $InputExt = [IO.Path]::GetExtension($_) if ($RepeatCount) { $Header = Get-Content $_ -TotalCount:$RepeatCount } # get the input file in manageable chunks $Part = 1 $buffer = "" Get-Content $_ -ReadCount:1 | %{ # make an output filename with a suffix $OutputFile = Join-Path $Destination ('{0}-{1:0000}{2}' -f ($InputName,$Part,$InputExt)) # In the first iteration the header will be # copied to the output file as usual # on subsequent iterations we have to do it if ($RepeatCount -and $Part -gt 1) { Set-Content $OutputFile $Header } # test buffer size and dump data only if buffer is greater than size if ($buffer.length -gt ($Size * 1MB)) { # write this chunk to the output file Write-Host "Writing $OutputFile" Add-Content $OutputFile $buffer $Part += 1 $buffer = "" } else { $buffer += $_ + "`r" } } } } } } 

Сделай это:

ФАЙЛ 1

Существует также этот быстрый (и несколько грязный) однострочный:

  $linecount=0; $i=0; Get-Content .\BIG_LOG_FILE.txt | % { Add-Content OUT$i.log "$_"; $linecount++; if ($linecount -eq 3000) {$I++; $linecount=0 } } 

Вы можете настроить количество первых строк на каждую партию, изменив твердое значение 3000.

 Get-Content C:\TEMP\DATA\split\splitme.txt | Select -First 5000 | out-File C:\temp\file1.txt -Encoding ASCII 

ФАЙЛ 2

 Get-Content C:\TEMP\DATA\split\splitme.txt | Select -Skip 5000 | Select -First 5000 | out-File C:\temp\file2.txt -Encoding ASCII 

ФАЙЛ 3

 Get-Content C:\TEMP\DATA\split\splitme.txt | Select -Skip 10000 | Select -First 5000 | out-File C:\temp\file3.txt -Encoding ASCII 

и т.д…

Звучит как работа для разделения команд UNIX:

 split MyBigFile.csv 

Просто разделите мой 55-гигабайтный CSV-файл на 21 тыс. Кусков менее чем за 10 минут.

Однако он не является родным для PowerShell, но поставляется с, например, git для пакета windows https://git-scm.com/download/win

Мое требование было несколько иным. Я часто работаю с файлами с разделителями-запятыми и с разделителями табуляции ASCII, где одна строка является одной записью данных. И они действительно большие, поэтому мне нужно разбить их на управляемые части (при сохранении строки заголовка).

Итак, я вернулся к своему classическому методу VBScript и собрал небольшой скрипт .vbs, который можно запустить на любом компьютере под управлением Windows (он автоматически запускается движком хоста сценария WScript.exe в окне).

Преимущество этого метода заключается в том, что он использует текстовые streamи, поэтому базовые данные не загружаются в память (или, по крайней мере, не все одновременно). В результате это происходит исключительно быстро, и на самом деле не нужно много памяти для запуска. Тестовый файл, который я только что разделил, используя этот скрипт на моем i7, составлял около 1 ГБ в размере файла, имел около 12 миллионов строк текста и был разделен на 25 файлов частей (каждая из которых имела около 500 тыс. Строк каждая) – обработка заняла около 2 минут и он не переходил на 3 МБ памяти, используемой в любой момент.

Здесь предостережение заключается в том, что он опирается на текстовый файл, имеющий «строки» (что означает, что каждая запись разделена CRLF), поскольку объект Text Stream использует функцию «ReadLine» для обработки одной строки за раз. Но, если вы работаете с TSV или CSV-файлами, это прекрасно.

 Option Explicit Private Const INPUT_TEXT_FILE = "c:\bigtextfile.txt" Private Const REPEAT_HEADER_ROW = True Private Const LINES_PER_PART = 500000 Dim oFileSystem, oInputFile, oOutputFile, iOutputFile, iLineCounter, sHeaderLine, sLine, sFileExt, sStart sStart = Now() sFileExt = Right(INPUT_TEXT_FILE,Len(INPUT_TEXT_FILE)-InstrRev(INPUT_TEXT_FILE,".")+1) iLineCounter = 0 iOutputFile = 1 Set oFileSystem = CreateObject("Scripting.FileSystemObject") Set oInputFile = oFileSystem.OpenTextFile(INPUT_TEXT_FILE, 1, False) Set oOutputFile = oFileSystem.OpenTextFile(Replace(INPUT_TEXT_FILE, sFileExt, "_" & iOutputFile & sFileExt), 2, True) If REPEAT_HEADER_ROW Then iLineCounter = 1 sHeaderLine = oInputFile.ReadLine() Call oOutputFile.WriteLine(sHeaderLine) End If Do While Not oInputFile.AtEndOfStream sLine = oInputFile.ReadLine() Call oOutputFile.WriteLine(sLine) iLineCounter = iLineCounter + 1 If iLineCounter Mod LINES_PER_PART = 0 Then iOutputFile = iOutputFile + 1 Call oOutputFile.Close() Set oOutputFile = oFileSystem.OpenTextFile(Replace(INPUT_TEXT_FILE, sFileExt, "_" & iOutputFile & sFileExt), 2, True) If REPEAT_HEADER_ROW Then Call oOutputFile.WriteLine(sHeaderLine) End If End If Loop Call oInputFile.Close() Call oOutputFile.Close() Set oFileSystem = Nothing Call MsgBox("Done" & vbCrLf & "Lines Processed:" & iLineCounter & vbCrLf & "Part Files: " & iOutputFile & vbCrLf & "Start Time: " & sStart & vbCrLf & "Finish Time: " & Now()) 

Поскольку строки могут быть переменными в журналах, я думал, что лучше всего использовать несколько строк для каждого файла. Следующий fragment кода обработал 4 миллиона файлов журнала строк менее чем за 19 секунд (18,83 секунды), разделив его на 500 000 строк:

 $sourceFile = "c:\myfolder\mylargeTextyFile.csv" $partNumber = 1 $batchSize = 500000 $pathAndFilename = "c:\myfolder\mylargeTextyFile part $partNumber file.csv" [System.Text.Encoding]$enc = [System.Text.Encoding]::GetEncoding(65001) # utf8 this one $fs=New-Object System.IO.FileStream ($sourceFile,"OpenOrCreate", "Read", "ReadWrite",8,"None") $streamIn=New-Object System.IO.StreamReader($fs, $enc) $streamout = new-object System.IO.StreamWriter $pathAndFilename $line = $streamIn.readline() $counter = 0 while ($line -ne $null) { $streamout.writeline($line) $counter +=1 if ($counter -eq $batchsize) { $partNumber+=1 $counter =0 $streamOut.close() $pathAndFilename = "c:\myfolder\mylargeTextyFile part $partNumber file.csv" $streamout = new-object System.IO.StreamWriter $pathAndFilename } $line = $streamIn.readline() } $streamin.close() $streamout.close() 

Это можно легко превратить в файл функции или скрипта с параметрами, чтобы сделать его более универсальным. Он использует StreamReader и StreamWriter для достижения скорости и StreamReader занимаемой памяти

Вот мое решение разделить файл patch6.txt (около 32 000 строк) на отдельные файлы по 1000 строк каждый. Это не быстро, но он выполняет эту работу.

 $infile = "D:\Malcolm\Test\patch6.txt" $path = "D:\Malcolm\Test\" $lineCount = 1 $fileCount = 1 foreach ($computername in get-content $infile) { write $computername | out-file -Append $path_$fileCount".txt" $lineCount++ if ($lineCount -eq 1000) { $fileCount++ $lineCount = 1 } } 
  • Как переименовать файлы в пакете с помощью счетчика?
  • Как переименовать некоторые файлы в соответствии с их меткой времени
  • Как я могу прочитать внутреннее создание / изменение PDF в Windows PowerShell?
  • Как получить текущий каталог выполняемого командлета
  • PowerShell говорит, что «выполнение скриптов отключено в этой системе».
  • Запуск PowerShell в планировщике заданий
  • Скопировать содержимое кнопок в текстовое поле?
  • Что я должен использовать: «Write-Host», «Write-Output» или « :: WriteLine»?
  • Запланированное задание Windows 10 выполняется как пользователь admin не запускает powershell
  • Многострочное регулярное выражение для соответствия конфигурационному блоку
  • Заменить пакет приложения на Windows 10
  • Interesting Posts

    Обратный отсчет персонажей, как на twitter

    Вопросы об объекте Entity Framework Context Lifetime

    Неужели беспроводной ретранслятор замедляет работу для всех?

    Как создать HTML-сообщение об ошибках проверки формы HTML с помощью CSS?

    в каком порядке статические блоки и статические переменные в classе выполняются?

    Обратные ключи и значения HashMap в Java

    Перезапустите общий доступ к Windows / Samba в Linux при запуске Windows Server (re)

    Максимальный предел примитивного типа Java Long

    Как hash и сравнить функцию-указатель-член?

    Как вызвать метод ежедневно, в определенное время, на C #?

    Отключить определенные разделы аудиофайла с помощью ffmpeg

    Как я могу разблокировать документ Microsoft .docx?

    Можно ли реплицировать автоматическое числовое преобразование Swifts в Foundation (NSNumber) для типов (U) Int8 / 16/32/64?

    Как отключить и включить порт USB через командную строку?

    Win 8.1 shutdown задерживает ровно 5 минут без каких-либо журналов

    Давайте будем гением компьютера.