Как обрабатывать файл в PowerShell по очереди в потоке

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

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

Итак, мой вопрос состоит из двух частей:

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

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

Ответ 1

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

Ответ 2

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

Ответ 3

Если вы хотите использовать прямое PowerShell, проверьте приведенный ниже код.

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