web-dev-qa-db-ja.com

PowerShellでファイルを1行ずつストリームとして処理する方法

私はいくつかのマルチギガバイトのテキストファイルで作業しており、PowerShellを使用してストリーム処理を行いたいと思っています。それは単純なもので、各行を解析してデータを引き出し、データベースに保存するだけです。

残念ながら、get-content | %{ whatever($_) }は、パイプのこの段階で行のセット全体をメモリに保持しているようです。また、驚くほど遅く、実際にすべてを読むのに非常に長い時間がかかります。

したがって、私の質問は2つの部分です。

  1. ストリーム全体を1行ずつ処理し、全体をメモリにバッファリングしないようにするにはどうすればよいですか?この目的でRAMのいくつかのギグを使い果たすことは避けたいと思います。
  2. どうすればより速く実行できますか? get-contentに対するPowerShellの反復処理は、C#スクリプトの100倍遅いようです。

-LineBufferSizeパラメータが見つからないなど、ここでやっていることが何かおかしいことを願っています...

86
scobi

本当に数ギガバイトのテキストファイルで作業する場合は、PowerShellを使用しないでください。それを読む方法を見つけたとしても、とにかく大量の行の処理がPowerShellで遅くなり、これを避けることはできません。単純なループでさえも、たとえば1,000万回の反復(非常に現実的な場合)には費用がかかります。

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

PDATE:それでも怖くない場合は、.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()
}

更新2

おそらくより良い/短いコードについてのコメントがあります。 forを含む元のコードには何も問題はなく、擬似コードではありません。しかし、読み取りループの短い(最短?)バリアントは

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

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

49
Despertar

PowerShellをそのまま使用する場合は、以下のコードを確認してください。

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