Как обрабатывать файл в PowerShell построчно как поток


Я работаю с некоторыми многогигабайтными текстовыми файлами и хочу сделать некоторую потоковую обработку на них с помощью PowerShell. Это простой материал, просто разбирая каждую строку и вытаскивая некоторые данные, а затем сохраняя их в базе данных.

к сожалению, get-content | %{ whatever($_) } кажется, чтобы сохранить весь набор линий на этом этапе трубы в памяти. Это также удивительно медленно, занимая очень много времени, чтобы на самом деле прочитать все это.

Так что мой вопрос-это два части:

  1. как я могу заставить его обрабатывать поток строка за строкой и не хранить всю вещь в буфере в памяти? Я хотел бы избежать использования нескольких гигабайт оперативной памяти для этой цели.
  2. как я могу заставить его работать быстрее? PowerShell итерации над get-content Кажется, в 100 раз медленнее, чем сценарий C#.

Я надеюсь, что есть что-то глупое, что я делаю здесь, например, пропуская -LineBufferSize параметр или что-то...

3 81

3 ответа:

если вы действительно собираетесь работать с многогигабайтными текстовыми файлами, не используйте 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
}

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
}