Cách xử lý một tệp trong PowerShell từng dòng dưới dạng một luồng


87

Tôi đang làm việc với một số tệp văn bản nhiều gigabyte và muốn thực hiện một số xử lý luồng trên chúng bằng PowerShell. Đó là công cụ đơn giản, chỉ cần phân tích cú pháp từng dòng và rút ra một số dữ liệu, sau đó lưu trữ nó trong cơ sở dữ liệu.

Thật không may, get-content | %{ whatever($_) }dường như giữ toàn bộ tập hợp các dòng ở giai đoạn này của đường ống trong bộ nhớ. Nó cũng chậm một cách đáng ngạc nhiên, mất rất nhiều thời gian để thực sự đọc hết.

Vì vậy, câu hỏi của tôi gồm hai phần:

  1. Làm cách nào để tôi có thể làm cho nó xử lý từng dòng một và không giữ toàn bộ nội dung được lưu vào bộ nhớ? Tôi muốn tránh sử dụng nhiều hợp đồng RAM cho mục đích này.
  2. Làm cách nào để làm cho nó chạy nhanh hơn? PowerShell lặp lại trên một get-contenttập lệnh C # có vẻ chậm hơn 100 lần.

Tôi hy vọng có điều gì đó ngu ngốc mà tôi đang làm ở đây, chẳng hạn như thiếu một -LineBufferSizetham số hoặc một cái gì đó ...


9
Để tăng tốc get-content, hãy đặt -ReadCount thành 512. Lưu ý rằng tại thời điểm này, $ _ trong Foreach sẽ là một mảng các chuỗi.
Keith Hill,

1
Tuy nhiên, tôi vẫn muốn sử dụng trình đọc .NET của Roman - nhanh hơn nhiều.
Keith Hill,

Vì tò mò, điều gì sẽ xảy ra nếu tôi không quan tâm đến tốc độ mà chỉ quan tâm đến bộ nhớ? Nhiều khả năng tôi sẽ sử dụng đề xuất trình đọc .NET, nhưng tôi cũng muốn biết cách giữ cho nó không lưu toàn bộ đường ống vào bộ nhớ.
Scobi

7
Để giảm thiểu bộ đệm, tránh gán kết quả của Get-Contentcho một biến vì điều đó sẽ tải toàn bộ tệp vào bộ nhớ. Theo mặc định, trong một Get-Contentđường trục , xử lý tệp một dòng tại một thời điểm. Miễn là bạn không tích lũy kết quả hoặc sử dụng một lệnh ghép ngắn được tích lũy bên trong (như Sắp xếp-Đối tượng và Nhóm-Đối tượng) thì bộ nhớ sẽ không quá tệ. Foreach-Object (%) là một cách an toàn để xử lý từng dòng, từng dòng một.
Keith Hill,

2
@dwarfsoft điều đó không có ý nghĩa gì. Khối -End chỉ chạy một lần sau khi tất cả quá trình xử lý được thực hiện. Bạn có thể thấy rằng nếu bạn cố gắng sử dụng get-content | % -End { }thì nó sẽ phàn nàn vì bạn chưa cung cấp khối quy trình. Vì vậy, nó không thể được sử dụng -End theo mặc định, nó phải được sử dụng -Process theo mặc định. Và thử 1..5 | % -process { } -end { 'q' }và thấy rằng khối cuối chỉ xảy ra một lần, thông thường gc | % { $_ }sẽ không làm việc nếu scriptblock mặc định để trở thành -end ...
TessellatingHeckler

Câu trả lời:


92

Nếu bạn thực sự chuẩn bị làm việc trên các tệp văn bản nhiều gigabyte thì không sử dụng PowerShell. Ngay cả khi bạn tìm ra cách để đọc nó nhanh hơn thì việc xử lý một lượng lớn dòng sẽ bị chậm trong PowerShell và bạn không thể tránh được điều này. Ngay cả các vòng lặp đơn giản cũng đắt tiền, giả sử với 10 triệu lần lặp (khá thực tế trong trường hợp của bạn), chúng tôi có:

# "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' } }

CẬP NHẬT: Nếu bạn vẫn không sợ hãi thì hãy thử sử dụng trình đọc .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()
}

CẬP NHẬT 2

Có nhận xét về mã có thể tốt hơn / ngắn hơn. Không có gì sai với mã gốc forvà nó không phải là mã giả. Nhưng biến thể ngắn hơn (ngắn nhất?) Của vòng lặp đọc là

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

3
FYI, biên dịch tập lệnh trong PowerShell V3 cải thiện tình hình một chút. Vòng lặp "công việc thực tế" đã tăng từ 117 giây trên V2 xuống 62 giây trên V3 được nhập trên bảng điều khiển. Khi tôi đặt vòng lặp vào một tập lệnh và đo việc thực thi tập lệnh trên V3, nó giảm xuống còn 34 giây.
Keith Hill

Tôi đặt cả ba bài kiểm tra trong một tập lệnh và nhận được những kết quả sau: V3 Beta: 20/27/83 giây; V2: 14/21/101. Có vẻ như trong thử nghiệm của tôi, V3 nhanh hơn trong thử nghiệm 3 nhưng nó khá chậm hơn trong hai phần đầu. Chà, đó là bản Beta, hy vọng hiệu suất sẽ được cải thiện trong RTM.
Roman Kuzmin

tại sao mọi người cứ khăng khăng sử dụng ngắt trong một vòng lặp như vậy. Tại sao không sử dụng một vòng lặp mà không yêu cầu nó, và đọc tốt hơn chẳng hạn như thay thế cho vòng lặp vớido { $line = $reader.ReadLine(); $line } while ($line -neq $null)
BeowulfNode42

1
Rất tiếc, điều đó được cho là -ne vì không bằng. Vòng lặp do.. while cụ thể đó có vấn đề là giá trị null ở cuối tệp sẽ được xử lý (trong trường hợp này là đầu ra). Để làm việc xung quanh đó quá bạn có thể cófor ( $line = $reader.ReadLine(); $line -ne $null; $line = $reader.ReadLine() ) { $line }
BeowulfNode42

4
@ BeowulfNode42, chúng ta có thể làm được điều này thậm chí còn ngắn hơn: while($null -ne ($line = $read.ReadLine())) {$line}. Nhưng chủ đề không thực sự là về những thứ như vậy.
Roman Kuzmin

51

System.IO.File.ReadLines()là hoàn hảo cho kịch bản này. Nó trả về tất cả các dòng của một tệp, nhưng cho phép bạn bắt đầu lặp lại các dòng ngay lập tức, có nghĩa là nó không phải lưu trữ toàn bộ nội dung trong bộ nhớ.

Yêu cầu .NET 4.0 trở lên.

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

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


6
Cần lưu ý: .NET Framework - Được hỗ trợ trong: 4.5, 4. Do đó, điều này có thể không hoạt động trong V2 hoặc V1 trên một số máy.
Roman Kuzmin

Điều này đã cho tôi System.IO.File không tồn tại lỗi, nhưng mã trên bằng cách La Mã làm việc cho tôi
Kolob Canyon

Đây chỉ là những gì tôi cần và dễ dàng đưa thẳng vào tập lệnh powershell hiện có.
user1751825 19/03/19

5

Nếu bạn muốn sử dụng PowerShell đơn giản, hãy xem đoạn mã dưới đây.

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

16
Đó là những gì OP muốn loại bỏ vì Get-Contentrất chậm trên các tệp lớn.
Roman Kuzmin
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.