PowerShell에서 파일을 한 줄씩 스트림으로 처리하는 방법


87

몇 기가 바이트 텍스트 파일로 작업 중이며 PowerShell을 사용하여 스트림 처리를 수행하고 싶습니다. 각 줄을 구문 분석하고 일부 데이터를 추출한 다음 데이터베이스에 저장하는 간단한 작업입니다.

불행히도 get-content | %{ whatever($_) }파이프의이 단계에서 전체 라인 세트를 메모리에 유지하는 것으로 보입니다. 또한 놀랍도록 느려 실제로 모든 것을 읽는 데 매우 오랜 시간이 걸립니다.

그래서 제 질문은 두 부분입니다.

  1. 전체를 메모리에 버퍼링하지 않고 줄 단위로 스트림을 처리하려면 어떻게해야합니까? 이 목적을 위해 몇 기가의 RAM을 사용하는 것을 피하고 싶습니다.
  2. 더 빨리 실행하려면 어떻게해야합니까? 을 반복하는 PowerShell get-content은 C # 스크립트보다 100 배 느립니다.

-LineBufferSize매개 변수를 놓친 것 같은 멍청한 일이 있기를 바랍니다 .


9
속도를 get-content높이 려면 -ReadCount를 512로 설정하십시오.이 시점에서 Foreach의 $ _는 문자열 배열이됩니다.
Keith Hill

1
그래도 .NET 리더를 사용 하자는 Roman의 제안을 따를 것입니다.
Keith Hill

호기심에서 속도에 신경 쓰지 않고 기억에만 신경 쓰면 어떻게 될까요? 대부분의 경우 .NET 독자 제안을 따르 겠지만 메모리의 전체 파이프를 버퍼링하지 않도록하는 방법도 알고 싶습니다.
scobi 2010

7
버퍼링을 최소화하려면 Get-Content전체 파일을 메모리에로드하므로 의 결과를 변수에 할당하지 마십시오 . 기본적으로 pipleline에서는 Get-Content파일을 한 번에 한 줄씩 처리합니다. 결과를 누적하지 않거나 내부적으로 누적되는 cmdlet (예 : Sort-Object 및 Group-Object)을 사용하지 않는 한 메모리 적중이 너무 나쁘지 않아야합니다. Foreach-Object (%)는 각 줄을 한 번에 하나씩 처리하는 안전한 방법입니다.
Keith Hill

2
@dwarfsoft는 말이되지 않습니다. -End 블록은 모든 처리가 완료된 후 한 번만 실행됩니다. 사용하려고 get-content | % -End { }하면 프로세스 블록을 제공하지 않았기 때문에 불평 하는 것을 알 수 있습니다 . 따라서 기본적으로 -End를 사용할 수 없으며 기본적으로 -Process를 사용해야합니다. 그리고 시도 1..5 | % -process { } -end { 'q' }및 엔드 블록은, 보통 한 번 발생 볼 gc | % { $_ }것없는 작업 스크립트 블록이 -end되는 디폴트로하면 ...
TessellatingHeckler

답변:


92

실제로 수 기가 바이트 텍스트 파일로 작업하려는 경우 PowerShell을 사용하지 마십시오. 읽는 방법을 찾더라도 엄청난 양의 줄을 더 빠르게 처리하는 것은 어쨌든 PowerShell에서 느리며 이것을 피할 수 없습니다. 단순한 루프조차도 비용이 많이 듭니다. 예를 들어 1,000 만 번의 반복 (귀하의 경우에는 실제)에 대해 다음과 같이합니다.

# "empty" loop: takes 10 seconds
measure-command { for($i=0; $i -lt 10000000; ++$i) {} }

# "simple" job, just output: takes 20 seconds
measure-command { for($i=0; $i -lt 10000000; ++$i) { $i } }

# "more real job": 107 seconds
measure-command { for($i=0; $i -lt 10000000; ++$i) { $i.ToString() -match '1' } }

업데이트 : 여전히 두렵지 않다면 .NET 리더를 사용해보십시오.

$reader = [System.IO.File]::OpenText("my.log")
try {
    for() {
        $line = $reader.ReadLine()
        if ($line -eq $null) { break }
        # process the line
        $line
    }
}
finally {
    $reader.Close()
}

업데이트 2

더 나은 / 짧은 코드에 대한 의견이 있습니다. 원래 코드에는 아무런 문제 for가 없으며 의사 코드가 아닙니다. 그러나 읽기 루프의 더 짧은 (가장 짧은?) 변형은 다음과 같습니다.

$reader = [System.IO.File]::OpenText("my.log")
while($null -ne ($line = $reader.ReadLine())) {
    $line
}

3
참고로 PowerShell V3의 스크립트 컴파일은 상황을 약간 개선합니다. "실제 작업"루프는 V2에서 117 초에서 콘솔에 입력 된 V3에서 62 초로 늘어났습니다. 루프를 스크립트에 넣고 V3에서 스크립트 실행을 측정하면 34 초로 떨어집니다.
Keith Hill

세 가지 테스트를 모두 스크립트에 넣어 다음과 같은 결과를 얻었습니다. V3 Beta : 20/27/83 초; V2 : 101 년 14 월 21 일. 내 실험에서 V3는 테스트 3에서 더 빠르지 만 처음 두 개에서는 상당히 느립니다. 음, 베타입니다. RTM에서 성능이 향상되기를 바랍니다.
Roman Kuzmin

사람들은 왜 그런 루프에서 브레이크를 사용한다고 주장합니까. 필요하지 않은 루프를 사용하지 않는 이유는 무엇이며 for 루프를 다음으로 대체하는 것과 같이 더 잘 읽습니다.do { $line = $reader.ReadLine(); $line } while ($line -neq $null)
BeowulfNode42

1
죄송합니다. 동일하지 않은 경우 -ne이어야합니다. 특정 do..while 루프에는 파일 끝에있는 null이 처리된다는 문제가 있습니다 (이 경우 출력). 너무 당신이 수를 해결하려면for ( $line = $reader.ReadLine(); $line -ne $null; $line = $reader.ReadLine() ) { $line }
BeowulfNode42

4
@ BeowulfNode42, 우리는 이것을 더 짧게 할 수 있습니다 : while($null -ne ($line = $read.ReadLine())) {$line}. 그러나 주제는 실제로 그런 것에 관한 것이 아닙니다.
로마 쿠즈 민

51

System.IO.File.ReadLines()이 시나리오에 완벽합니다. 파일의 모든 줄을 반환하지만 줄을 즉시 반복 할 수 있으므로 전체 내용을 메모리에 저장할 필요가 없습니다.

.NET 4.0 이상이 필요합니다.

foreach ($line in [System.IO.File]::ReadLines($filename)) {
    # do something with $line
}

http://msdn.microsoft.com/en-us/library/dd383503.aspx


6
참고 사항 : .NET Framework-지원 : 4.5, 4. 따라서 일부 컴퓨터의 V2 또는 V1에서는 작동하지 않을 수 있습니다.
로마 쿠즈 민

이것은 나를 위해 System.IO.File 오류가 존재하지 않는 듯했으나 로마에 의해 위의 코드는 나를 위해 일한
계산법 캐년

이것은 내가 필요로하는 것이었고 기존의 powershell 스크립트에 직접 쉽게 넣을 수있었습니다.
user1751825

5

직접 PowerShell을 사용하려면 아래 코드를 확인하십시오.

$content = Get-Content C:\Users\You\Documents\test.txt
foreach ($line in $content)
{
    Write-Host $line
}

16
그것은 Get-Content큰 파일에서 매우 느리기 때문에 OP가 제거하고 싶었던 것 입니다.
로마 쿠즈 민
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.