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.