PowerShell을 사용하여 텍스트 파일을 분할하려면 어떻게해야합니까?
큰 (500MB) 텍스트 파일 (log4net 예외 파일)을 100 개의 5MB 파일과 같은 관리 가능한 청크로 분할해야합니다.
PowerShell을위한 공원 산책이되어야한다고 생각합니다. 어떻게하니?
이는 표준 Get-Content cmdlet이 매우 큰 파일을 너무 잘 처리하지 않는다는 사실로 인해 복잡한 PowerShell의 다소 쉬운 작업입니다. .NET StreamReader 클래스 를 사용하여 PowerShell 스크립트에서 한 줄씩 파일을 읽고 Add-Content
cmdlet을 사용하여 파일 이름에 계속 증가하는 인덱스가있는 파일에 각 줄을 쓰는 것이 좋습니다. 이 같은:
$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.6GB 로그 파일의 경우 몇 시간 후에 포기하고 다음 날 출근하기 전에 완료되지 않을 것임을 깨달았습니다.
두 가지 문제 : Add-Content에 대한 호출 이 소스 파일의 모든 행에 대해 현재 대상 파일을 열고 검색 한 다음 닫습니다. 매번 소스 파일을 조금 읽고 새 줄을 찾는 것도 속도가 느려지지만 Add-Content가 주된 원인이라고 생각합니다.
다음 변형은 약간 덜 유쾌한 출력을 생성합니다. 줄 중간에 파일을 분할하지만 1.6GB 로그를 1 분 이내에 분할합니다.
$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를 사용하여 새 줄로 분할합니다 (전체 파일을 한 번에 메모리로 읽어 오는 대신 한 줄씩). 이 접근 방식은 내가 아는 가장 빠른 방법으로 큰 파일을 분할 할 수 있습니다.
참고 : 오류 검사는 거의 수행하지 않으므로 귀하의 경우에 원활하게 작동한다고 보장 할 수 없습니다. 그것은 내 것을 위해 했다 (95 초 동안 파일 당 100,000 줄로 분할 된 4 백만 줄의 1.7GB TXT 파일 ).
#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.7GB 파일을 분할하는 출력 :
...
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
나는 종종 같은 일을해야합니다. 트릭은 헤더가 각 분할 청크로 반복되는 것입니다. 다음 cmdlet (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
}
}
}
}
단일 vCard VCF 파일에서 여러 연락처를 별도의 파일로 분할하는 동안이 질문을 발견했습니다. Lee의 코드를 기반으로 한 작업입니다. 새 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()
이러한 답변 중 대부분은 내 소스 파일에 비해 너무 느 렸습니다. 내 소스 파일은 대략 동일한 줄 수의 파일로 분할해야하는 10MB에서 800MB 사이의 SQL 파일이었습니다.
Add-Content를 사용하는 이전 답변 중 일부가 매우 느립니다. 분할이 끝날 때까지 많은 시간을 기다리는 것은 드문 일이 아닙니다.
나는 Typhlosaurus의 대답을 시도 하지 않았지만 줄 수가 아닌 파일 크기로만 분할하는 것으로 보입니다.
다음은 내 목적에 적합합니다.
$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"
54MB 파일의 경우 출력이 표시됩니다.
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
55GB csv 파일을 10 분 이내에 21k 청크로 분할하면됩니다.
하지만 PowerShell에 기본적으로 제공되지는 않지만 예를 들어 Windows 용 git 패키지 https://git-scm.com/download/win 과 함께 제공됩니다.
내 요구 사항은 약간 달랐습니다. 저는 종종 한 줄이 데이터의 단일 레코드 인 쉼표로 구분 된 및 탭으로 구분 된 ASCII 파일로 작업합니다. 그리고 그것들은 정말 크기 때문에 머리글 행을 유지하면서 관리 가능한 부분으로 분할해야합니다.
그래서 저는 고전적인 VBScript 방법으로 되돌아가 모든 Windows 컴퓨터에서 실행할 수있는 작은 .vbs 스크립트를 함께 묶었습니다 (Windows의 WScript.exe 스크립트 호스트 엔진에 의해 자동으로 실행 됨).
이 방법의 이점은 텍스트 스트림을 사용하므로 기본 데이터가 메모리에로드되지 않거나 적어도 한 번에 모두로드되지 않는다는 것입니다. 그 결과 매우 빠르며 실제로 실행하는 데 많은 메모리가 필요하지 않습니다. i7에서이 스크립트를 사용하여 방금 분할 한 테스트 파일은 파일 크기가 약 1GB이고 텍스트가 약 1,200 만 줄이며 25 개의 부분 파일 (각각 약 500k 줄)로 분할되었습니다. 처리하는 데 약 2 분이 걸렸습니다. 어느 시점에서도 3MB 이상의 메모리를 사용하지 않았습니다.
여기서주의 할 점은 Text Stream 개체가 "ReadLine"기능을 사용하여 한 번에 한 줄을 처리하기 때문에 "줄"(각 레코드가 CRLF로 구분됨을 의미)이있는 텍스트 파일에 의존한다는 것입니다. 그러나 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())
As the lines can be variable in logs I thought it best to take a number of lines per file approach. The following code snippet processed a 4 million line log file in under 19 seconds (18.83.. seconds)splitting it into 500,000 line chunks:
$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()
This can easily be turned into a function or script file with parameters to make it more versatile. It uses a StreamReader
and StreamWriter
to achieve its speed and tiny memory footprint
Here is my solution to split a file called patch6.txt (about 32,000 lines) into separate files of 1000 lines each. Its not quick, but it does the job.
$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
}
}
참고URL : https://stackoverflow.com/questions/1001776/how-can-i-split-a-text-file-using-powershell
'IT Share you' 카테고리의 다른 글
Java에서 "enum"의 사용은 무엇입니까? (0) | 2020.11.12 |
---|---|
JQuery로 드롭 다운 목록 지우기 (0) | 2020.11.12 |
다른 테이블을 기반으로 한 테이블의 모든 행 삭제 (0) | 2020.11.12 |
주어진 이름 '@ style / Theme.AppCompat.Light'와 일치하는 리소스를 찾을 수 없습니다. (0) | 2020.11.11 |
Snap.svg 대 Svg.js (0) | 2020.11.11 |