PowerShell で大容量のテキストファイルを処理する方法(PowerShellで1行読み込みを行う方法)


対象読者

  • 事務作業などでPowerShellを利用しデータの加工を行なっている人
  • 中小規模のシステムにて運用ツールの作成を行なっている人(特にテキストログの処理を行いたい場合など)

読者の到達目標

PowerShellにてフリーズするような大容量のファイルを、フリーズしないサイズに分割し加工できるようになっていただけたらと思います。
PowerShellをゼロから書けない方でもサンプルコードを基に操作ができるよう詳細に記載しました。

事象と発生する問題、解決すべき課題

PowerShellにてテキストファイルを読み込んだ場合、1MBのファイルだとしても
メタ情報が多数付与されることで、そこそこメモリを消費してしまう。

これにより数百MB、数GBとなるとOutOfMemoryが発生しまともに処理できなります。
これに対応するために、PowerShellにて大容量ファイルの処理方法をまとめます。

解決方法

上記のような場合に、テキストファイルをすべてメモリにロードしてしまうとエラーとなってしまいます。
そのため、1行ずつ処理し行中から取得した情報単位にファイルを分割し直すことで
処理可能なファイルサイズにし対処します。

以下にサンプルコードを示します。
検証環境:Windows10 Powershell v5.1
※Windows PowerShell ポケットリファレンスでは、v1~v3まで対応していると
記載されていますのでv5までのすべてのバージョンで利用できるかと思います。

testData.csv
国名,牛肉(くず肉含む)_第一単位,牛肉(くず肉含む)_第一数量,牛肉(くず肉含む)第二単位,牛肉(くず肉含む)_第二数量,牛肉(くず肉含む)_金額(千円),内牛肉(くず肉除く)_第一単位,内牛肉(くず肉除く)_第一数量,内牛肉(くず肉除く)_第二単位,内牛肉(くず肉除く)_第二数量,内牛肉(くず肉除く)_金額(千円),内牛肉(くず肉)_第一単位,内牛肉(くず肉)_第一数量,内牛肉(くず肉)_第二単位,内牛肉(くず肉)_第二数量,内牛肉(くず肉)_金額(千円)
世界,,0,KG,41603500,27049397,,0,KG,41543781,27009997,,0,KG,59719,39400
デンマーク,,0,KG,0,0,,0,KG,0,0,,0,KG,0,0
アイルランド,,0,KG,23722,9577,,0,KG,23722,9577,,0,KG,0,0
オランダ,,0,KG,5750,7436,,0,KG,5750,7436,,0,KG,0,0
フランス,,0,KG,9393,19435,,0,KG,9095,19187,,0,KG,298,248
イタリア,,0,KG,2724,5972,,0,KG,2724,5972,,0,KG,0,0
ポーランド,,0,KG,0,0,,0,KG,0,0,,0,KG,0,0
ハンガリー,,0,KG,0,0,,0,KG,0,0,,0,KG,0,0
カナダ,,0,KG,657099,329262,,0,KG,657099,329262,,0,KG,0,0
アメリカ合衆国,,0,KG,17741367,12157552,,0,KG,17741367,12157552,,0,KG,0,0
メキシコ,,0,KG,957074,492920,,0,KG,957074,492920,,0,KG,0,0
ニカラグア,,0,KG,0,0,,0,KG,0,0,,0,KG,0,0
パナマ,,0,KG,6286,2689,,0,KG,6286,2689,,0,KG,0,0
チリ,,0,KG,9862,8523,,0,KG,3003,4707,,0,KG,6859,3816
オーストラリア,,0,KG,21341155,13308503,,0,KG,21288593,13273167,,0,KG,52562,35336
ニュージーランド,,0,KG,849068,707528,,0,KG,849068,707528,,0,KG,0,0
バヌアツ,,0,KG,0,0,,0,KG,0,0,,0,KG,0,0

出典:政府統計の総合窓口(e-Stat)(http://www.e-stat.go.jp/)
「貿易統計(輸出)1農産物(畜産品)牛肉」のデータを扱いやすいようExcel形式からCSVの形式に編集しています。
※注 テストデータ用なのでデータ自体を利用したい場合は原本を取得ください。
原本のURL(https://www.e-stat.go.jp/stat-search/files?page=1&query=%E7%89%9B%E8%82%89&layout=dataset&stat_infid=000031663012)

LargeFileSplit.ps1
    $inputfile = ".\testData.txt"
    $outPath = ".\output\testData_after_"
    $extension = ".txt"
    Get-Content -ReadCount 1 $inputfile -Encoding UTF8 | ForEach-Object {
        $line = $_ -split ','
        $outfile = $outPath + $line[0] + $extension
        # 何かしらの編集処理 後で編集方法も整理したい
        Out-File $outfile -Encoding UTF8 -Append -NoClobber -InputObject $_
    }

実行結果

テストデータでは、1カラム目の国名を取得し、国名単位でファイルを作成しています。
分割の対象とするデータは、適宜選択してください。

Get-Contentのオプションで利用している「-ReadCount」がポイントになります。
このオプションによって、パイプラインに送る行数を指定することができます。
※1行にしないと型が変わってしまい、データの加工ができなくなります。

また、Import-Csvではこのオプションに対応できないため、
サンプルでは、CSVファイルを処理する場合を考慮し、カンマ区切りにてデータを取得する方法としています。

ファイルにヘッダーを付与したい場合は、以下のようにTest-Pathにて
ファイルの有無を確認し、なければファイルを作りヘッダーを付与することで対応します。

LargeFileSplitKai.ps1
    $inputfile = ".\testData.txt"
    $outPath = ".\output\testData_after_"
    $extension = ".txt"
    Get-Content -ReadCount 1 $inputfile -Encoding UTF8 | ForEach-Object {
        $line = $_ -split ','
        $outfile = $outPath + $line[0] + $extension
        # 何かしらの編集処理
        # 下のif文で初回書き込みかチェックしヘッダをつけることも可能
        if(!(Test-Path -Path $outfile)){
            Out-File $outfile -Encoding UTF8 -Append -NoClobber -InputObject $header
        }
        Out-File $outfile -Encoding UTF8 -Append -NoClobber -InputObject $_  
    }

参考

参考として利用したリンク、書籍を記載します。

リンク

MicroSoftのGet-Contentリファレンス
e-Stat 政府統計の総合窓口

書籍

Windows PowerShell ポケットリファレンス 牟田口 大介(著):技術評論社