1. ホーム
  2. powershell

[解決済み] PowerShellでファイルをストリームとして一行ずつ処理する方法

2023-01-19 12:13:37

質問

数ギガバイトのテキストファイルを扱っていて、PowerShellを使用してそれらのストリーム処理を行いたいと考えています。それは単純なもので、各行をパースしていくつかのデータを取り出し、それをデータベースに格納するだけです。

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

だから私の質問は2つの部分です。

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

何か馬鹿なことをしているのではと期待しているのですが、例えば -LineBufferSize パラメータがないとか、そういうことだといいのですが...。

どのように解決するのですか?

もし、本当に数ギガバイトのテキストファイルを扱うのであれば、PowerShellは使わないでください。たとえ高速に読み込む方法を見つけたとしても、PowerShell では膨大な量の行の処理はとにかく遅く、これを避けることはできません。単純なループでさえも、例えば1000万回の反復処理(あなたのケースではかなり現実的です)にはコストがかかります。

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

UPDATEです。 もし、まだ怖くないのであれば、.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
}