PowerShell'de bir dosyayı akış olarak satır satır işleme


90

Bazı çok gigabaytlık metin dosyalarıyla çalışıyorum ve PowerShell'i kullanarak bunlarda bazı akış işlemleri yapmak istiyorum. Basit şeyler, sadece her satırı ayrıştırmak ve bazı verileri çıkarmak ve ardından bir veritabanında depolamak.

Ne yazık ki, get-content | %{ whatever($_) }borunun bu aşamasında tüm satır kümesini bellekte tutuyor gibi görünüyor. Aynı zamanda şaşırtıcı derecede yavaştır, aslında hepsini okumak çok uzun zaman alır.

Yani sorum iki bölümden oluşuyor:

  1. Akımı satır satır işlemesini ve her şeyi hafızada tutmamasını nasıl sağlayabilirim? Bu amaçla birkaç GB RAM kullanmaktan kaçınmak istiyorum.
  2. Daha hızlı çalışmasını nasıl sağlayabilirim? Bir üzerinde yinelenen PowerShell get-content, bir C # betiğinden 100 kat daha yavaş görünüyor.

Umarım burada yaptığım aptalca bir şey vardır, bir -LineBufferSizeparametreyi kaçırmak gibi ...


9
Hızlandırmak için get-contentyukarı, bu noktada, foreachta $ _ dizelerden oluşan bir dizi olacağını 512. Not -ReadCount ayarlayın.
Keith Tepesi

1
Yine de, Roman'ın .NET okuyucuyu kullanma önerisine uyardım - çok daha hızlı.
Keith Tepesi

Merak ettiğim için, hıza değil de sadece hafızaya önem verirsem ne olur? Büyük olasılıkla .NET okuyucu önerisiyle gideceğim, ancak aynı zamanda tüm boruyu bellekte arabelleğe almasını nasıl önleyeceğimi de bilmekle ilgileniyorum.
scobi

7
Arabelleğe almayı en aza indirmek için, sonucunu Get-Contentbir değişkene atamaktan kaçının çünkü bu, tüm dosyayı belleğe yükleyecektir. Varsayılan olarak, Get-Contentbir hat hattında, dosyayı her seferinde bir satır işler. Sonuçları biriktirmediğiniz veya dahili olarak biriken bir cmdlet kullanmadığınız sürece (Sıralama-Nesne ve Grup-Nesne gibi), bellek vuruşu çok kötü olmamalıdır. Foreach-Object (%), her satırı birer birer işlemenin güvenli bir yoludur.
Keith Hill

2
@dwarfsoft bu hiç mantıklı değil. -End bloğu, tüm işlemler tamamlandıktan sonra yalnızca bir kez çalışır. Kullanmaya çalışırsanız, get-content | % -End { }bir işlem bloğu sağlamadığınız için şikayet ettiğini görebilirsiniz. Bu nedenle varsayılan olarak -End kullanılamaz, varsayılan olarak -Process kullanmalıdır. Ve denemek 1..5 | % -process { } -end { 'q' }ve bitiş bloğu sadece, olağan bir kez olur görüyoruz gc | % { $_ }olmaz iş scriptblock -SON olmasının temerrüde düşerse ...
TessellatingHeckler

Yanıtlar:


93

Gerçekten çok gigabaytlık metin dosyaları üzerinde çalışmak üzereyseniz, PowerShell'i kullanmayın. Okumanın bir yolunu bulsanız bile, çok sayıda satırın daha hızlı işlenmesi PowerShell'de zaten yavaş olacaktır ve bundan kaçınamazsınız. Basit döngüler bile pahalıdır, örneğin 10 milyon yineleme için (sizin durumunuzda oldukça gerçektir) bizde:

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

GÜNCELLEME: Hala korkmuyorsanız, .NET okuyucuyu kullanmayı deneyin:

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

GÜNCELLEME 2

Muhtemelen daha iyi / daha kısa kod hakkında yorumlar var. Orijinal kodda yanlış hiçbir şey yoktur forve sözde kod değildir. Ancak okuma döngüsünün daha kısa (en kısa?) Varyantı

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

3
Bilginize, PowerShell V3'teki komut dosyası derlemesi durumu biraz iyileştirir. "Gerçek iş" döngüsü, V2'de 117 saniyeden konsolda yazılan V3'te 62 saniyeye çıktı. Döngüyü bir komut dosyasına koyduğumda ve komut dosyası yürütmesini V3'te ölçtüğümde, 34 saniyeye düşüyor.
Keith Hill

Üç testi de bir komut dosyasına koydum ve şu sonuçları aldım: V3 Beta: 20/27/83 saniye; V2: 14/21/101. Benim deneyimde V3 test 3'te daha hızlı gibi görünüyor, ancak ilk ikisinde oldukça yavaş. Eh, Beta, umarım RTM'de performans iyileştirilir.
Roman Kuzmin

İnsanlar neden böyle bir döngüde bir ara kullanmakta ısrar ediyor? Neden gerektirmeyen ve for döngüsünü değiştirmek gibi daha iyi okuyan bir döngü do { $line = $reader.ReadLine(); $line } while ($line -neq $null)
kullanmıyorsunuz

1
oops bunun eşit olmadığı için -ne olması gerekiyor. Bu belirli do.. while döngüsü, dosyanın sonundaki boş değerin işleneceği sorununa sahiptir (bu durumda çıktı). Bunu çözmek için de sahip olabilirsinizfor ( $line = $reader.ReadLine(); $line -ne $null; $line = $reader.ReadLine() ) { $line }
BeowulfNode42

4
BeowulfNode42 @, bu da kısa yapabilirsiniz: while($null -ne ($line = $read.ReadLine())) {$line}. Ancak konu aslında böyle şeylerle ilgili değil.
Roman Kuzmin

52

System.IO.File.ReadLines()bu senaryo için mükemmeldir. Bir dosyanın tüm satırlarını döndürür, ancak satırlar üzerinde yinelemeye hemen başlamanıza izin verir, bu da tüm içeriği bellekte saklamak zorunda olmadığı anlamına gelir.

.NET 4.0 veya üstünü gerektirir.

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

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


6
Bir not gereklidir: .NET Framework - Desteklenen: 4.5, 4. Bu nedenle, bu bazı makinelerde V2 veya V1'de çalışmayabilir.
Roman Kuzmin

Bu bana System.IO.File hatası verdi, ancak Roman tarafından yukarıdaki kod benim için çalıştı
Kolob Canyon

Bu tam da ihtiyacım olan şeydi ve doğrudan mevcut bir powershell betiğine bırakmak kolaydı.
user1751825

5

Doğrudan PowerShell kullanmak istiyorsanız aşağıdaki kodu kontrol edin.

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

16
OP'nin kurtulmak istediği şey buydu çünkü Get-Contentbüyük dosyalarda çok yavaş.
Roman Kuzmin
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.