Я работаю с некоторыми текстовыми файлами размером несколько гигабайт и хочу обработать их потоком с помощью PowerShell. Это просто: нужно просто проанализировать каждую строку и извлечь некоторые данные, а затем сохранить их в базе данных.
К сожалению, get-content | %{ whatever($_) }
похоже, что на этом этапе конвейера в памяти сохраняется весь набор строк. К тому же это на удивление медленно, ведь на то, чтобы все это прочитать, уходит очень много времени.
Итак, мой вопрос состоит из двух частей:
- Как я могу заставить его обрабатывать поток построчно и не хранить все в буфере в памяти? Я бы не хотел использовать для этого несколько гигабайт оперативной памяти.
- Как заставить его работать быстрее? PowerShell, перебирающий a,
get-content
оказывается в 100 раз медленнее, чем сценарий C #.
Я надеюсь, что я здесь что-то делаю глупо, например, упустил -LineBufferSize
параметр или что-то в этом роде ...
powershell
stream
Скоби
источник
источник
get-content
установите -ReadCount равным 512. Обратите внимание, что на этом этапе $ _ в Foreach будет массивом строк.Get-Content
переменной, так как это загрузит весь файл в память. По умолчанию в конвейереGet-Content
обрабатывает файл по одной строке за раз. Пока вы не накапливаете результаты или не используете командлет, который накапливается внутри (например, Sort-Object и Group-Object), попадание в память не должно быть слишком большим. Foreach-Object (%) - это безопасный способ обрабатывать каждую строку по одной.get-content | % -End { }
он пожалуется, потому что вы не предоставили блок процесса. Таким образом, он не может использовать -End по умолчанию, он должен использовать -Process по умолчанию. И попытайтесь1..5 | % -process { } -end { 'q' }
увидеть, что конечный блок происходит только один раз, обычное делоgc | % { $_ }
не сработает, если бы блок сценария по умолчанию был -End ...Ответы:
Если вы действительно собираетесь работать с текстовыми файлами размером в несколько гигабайт, не используйте PowerShell. Даже если вы найдете способ прочитать его, более быстрая обработка огромного количества строк в PowerShell все равно будет медленной, и вы не сможете этого избежать. Даже простые циклы дороги, скажем, для 10 миллионов итераций (вполне реально в вашем случае) у нас есть:
# "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:
$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 }
источник
do { $line = $reader.ReadLine(); $line } while ($line -neq $null)
for ( $line = $reader.ReadLine(); $line -ne $null; $line = $reader.ReadLine() ) { $line }
while($null -ne ($line = $read.ReadLine())) {$line}
. Но тема не совсем о таких вещах.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
источник
Если вы хотите использовать обычный PowerShell, ознакомьтесь с приведенным ниже кодом.
$content = Get-Content C:\Users\You\Documents\test.txt foreach ($line in $content) { Write-Host $line }
источник
Get-Content
очень медленно работает с большими файлами.