【テクニカル・上級編】【超巨大ログ分割】数GBクラスのテキストをFSOで指定行数ごとに高速で小分けファイル出力する軽量バッチ – VBScript (Visual Basic Scripting Edition)解析バイブル

スポンサーリンク

【超巨大ログ分割】数GBクラスのテキストをFSOで指定行数ごとに高速で小分けファイル出力する軽量バッチ

レガシーシステムの現場において、数GBに肥大化したテキストログの存在は常にエンジニアの頭痛の種だ。GUIのテキストエディタで開こうものならメモリを喰らい尽くし、OSごとハングアップする。かといって、新規の開発環境を構築できない閉じたネットワーク環境では、手元にある道具――すなわちWindows標準の VBScript (Visual Basic Scripting Edition) でこの難局を切り抜けなければならない。

今回は、FileSystemObject (FSO) の `TextStream` を極限までチューニングし、メモリリークを完全に排除しながら、数GBの巨大ログを指定行数ごとに高速で分割するプロダクションレベルのバッチスクリプトを提示する。

—

1. FSOを用いた大容量ファイル処理の罠とアーキテクチャの要諦

VBScriptによるファイル操作の基本は `Scripting.FileSystemObject` であるが、素朴に実装すると以下の致命的な問題に直面する。

  • メモリバッファの肥大化: `ReadAll` や `Read` メソッドで巨大なデータを変数に抱え込もうとすれば、あっという間にVBScriptのプロセス(`cscript.exe` / `wscript.exe`)がメモリを枯渇させる。
  • COMオブジェクトの参照リーク: ループ内で安易に `CreateObject` を行ったり、参照の解放(`Set obj = Nothing`)を怠ると、VBScriptランタイムのガベージコレクションが追いつかず、メモリリークを引き起こす。
  • I/Oのボトルネック: 1行ごとにファイルを開閉するような愚行を犯せば、ディスクのヘッドが悲鳴を上げ、処理が数時間単位で終わらなくなる。

解決アプローチ

1. ストリーミング読み書きの徹底: `ReadLine` による1行ずつの逐次処理と、`WriteLine` による順次出力により、メモリ使用量を常に「1行分の文字列サイズ(数KB程度)」に固定する。
2. 適切なファイルハンドルの管理: 読み込み元のストリームは1つ、書き込み先のストリームも常に「現在アクティブな1つ」だけを維持し、ファイル切り替え時に確実にクローズ&解放を行う。
3. ゼロパディングされた連番生成: 1万ファイルを超える分割にも耐えられるよう、ファイル名を `log_00001.txt` のようにゼロパディングフォーマットで動的生成する。

—

2. 実装コード:極限のメモリ効率を実現する分割スクリプト

以下のコードを `SplitLog.vbs` として保存し、コマンドプロンプトから `cscript.exe SplitLog.vbs` として実行してほしい。WScriptではなく、必ずCStandard Script Host(`cscript`)を使用すること。

‘ ==============================================================================
‘ Script Name: SplitLargeLog.vbs
‘ Description: 数GBクラスの巨大ログを指定行数で高速かつメモリ効率良く分割する
‘ Architecture: Streaming I/O with FileSystemObject (FSO)
‘ ==============================================================================

Option Explicit

‘ — 設定パラメータ —
Const SOURCE_FILE = “C:\Logs\huge_application.log” ‘ 処理対象の巨大ログパス
Const OUTPUT_DIR = “C:\Logs\SplitChunks\” ‘ 分割後ファイルの出力先ディレクトリ
Const FILE_PREFIX = “log_chunk_” ‘ 分割ファイルのプレフィックス
Const MAX_LINES = 100000 ‘ 1ファイルあたりの最大行数(例: 10万行)

‘ 実行開始時間の取得(パフォーマンス計測用)
Dim startTime: startTime = Timer

Main

Sub Main()
Dim fso, folder
Set fso = CreateObject(“Scripting.FileSystemObject”)

‘ 前提条件チェック
If Not fso.FileExists(SOURCE_FILE) Then
WScript.Echo “[ERROR] 入力ファイルが存在しません: ” & SOURCE_FILE
Exit Sub
End If

‘ 出力先ディレクトリの確保
If Not fso.FolderExists(OUTPUT_DIR) Then
fso.CreateFolder(OUTPUT_DIR)
WScript.Echo “[INFO] 出力ディレクトリを作成しました: ” & OUTPUT_DIR
End If

WScript.Echo “[INFO] ログ分割処理を開始します…”

‘ コア処理の実行
Call SplitLogFile(fso, SOURCE_FILE, OUTPUT_DIR, FILE_PREFIX, MAX_LINES)

‘ 終了処理
Set fso = Nothing

Dim elapsedTime: elapsedTime = Timer – startTime
WScript.Echo “[SUCCESS] すべての処理が完了しました。処理時間: ” & FormatNumber(elapsedTime, 2) & ” 秒”
End Sub

Sub SplitLogFile(fso, srcPath, outDir, prefix, maxLines)
Const ForReading = 1
Const ForWriting = 2
Const TristateUseDefault = -2

Dim srcStream, outStream
Dim currentLineCount, fileIndex
Dim currentOutputPath

‘ 読み込み専用でソースを開く (TristateUseDefault = システムデフォルトエンコーディング)
Set srcStream = fso.OpenTextFile(srcPath, ForReading, False, TristateUseDefault)

currentLineCount = 0
fileIndex = 1
Set outStream = Nothing

‘ ストリームの終端 (AtEndOfStream) に達するまで1行ずつ処理
Do While Not srcStream.AtEndOfStream
‘ ファイル出力ストリームが未オープン(初回またはファイル切り替え後)の場合
If outStream Is Nothing Then
currentOutputPath = outDir & prefix & Right(“00000” & fileIndex, 5) & “.txt”
‘ 新規作成(True)モードでテキストストリームを開く
Set outStream = fso.OpenTextFile(currentOutputPath, ForWriting, True, TristateUseDefault)
WScript.Echo “[DEBUG] ファイル生成: ” & currentOutputPath
End If

‘ 1行読み込んで書き込み
outStream.WriteLine srcStream.ReadLine
currentLineCount = currentLineCount + 1

‘ 指定行数に達した場合、現在の出力ストリームを閉じて解放
If currentLineCount >= maxLines Then
outStream.Close
Set outStream = Nothing
currentLineCount = 0
fileIndex = fileIndex + 1
End If
Loop

‘ 最後に開いていた出力ストリームのクリーンアップ
If Not outStream Is Nothing Then
outStream.Close
Set outStream = Nothing
End If

‘ 読み込みストリームのクリーンアップ
srcStream.Close
Set srcStream = Nothing
End Sub

—

3. シニアエンジニアが押さえるべき設計上のポイント

① `TristateUseDefault` と文字コード問題

Windows環境のログは、Shift-JIS (CP932) や UTF-8 (BOM付き/BOMなし) が混在する。FSOの `OpenTextFile` の第4引数(Format)には注意が必要だ。

  • `TristateTrue`: Unicode (UTF-16LE)
  • `TristateFalse`: ASCII (Shift-JIS等)
  • `TristateUseDefault`: システムのデフォルト設定

もし対象ログがUTF-8(BOMなし)である場合、純粋なFSOでは文字化けを起こすか、正しく読み込めない場合がある。その場合は、ADODB.Streamオブジェクトを組み合わせるアーキテクチャへの移行を検討すべきだが、Shift-JISが支配的なレガシー基幹システムにおいては、上記のFSO実装が最もオーバヘッドが少なく高速に動作する。

② メモリリークを完全に防ぐオブジェクトの破棄戦略

VBScriptは参照カウント方式のガベージコレクションを採用している。
ループ内で `CreateObject` を行わないのは鉄則だが、それ以上に「使い終わった `TextStream` オブジェクトを即座に `.Close` し、変数に `Nothing` を代入する」ことが極限のパフォーマンス維持に直結する。
メモリの断片化(ヒープフラグメンテーション)を防ぎ、数時間に及ぶバッチ処理でもプロセスサイズが一定に保たれることを保証する。

③ ゼロパディングによるファイル順序の保証

`Right(“00000” & fileIndex, 5)` というイディオムにより、生成されるファイル名が `log_chunk_00001.txt`, `log_chunk_00002.txt` … となり、エクスプローラーやシェルでのファイルソート順が崩れないように担保している。文字列表現のソートで `log_chunk_10.txt` が `log_chunk_2.txt` の前に来てしまう致命的なインシデントを未然に防ぐプロの定石だ。

—

総括

現代のモダンな言語(PythonやPowerShellなど)であれば数行で書ける処理かもしれない。しかし、「何もインストールされていない、厳格にロックダウンされたWindows Serverの保守現場」において、VBScriptは今なお最強のインフラストラクチャであり続ける。

オブジェクトのライフサイクルを完全に掌握し、I/Oのボトルネックを極限まで削ぎ落としたこのスクリプトは、レガシーシステムの寿命を確実に延ばすための確かな武器となるはずだ。現場の安定稼働を祈る。

タイトルとURLをコピーしました