【改行コード一括クリーンアップ】ADODB.Stream と Split を用いた CRLF / LF / CR 混在テキストの高速標準化
レガシーシステムの深部、あるいは現代のマルチプラットフォーム環境において、最もエンジニアを疲弊させるものの一つが「改行コードの不一致」である。
Linux由来の `LF (0x0A)`、レガシーMac由来の `CR (0x0D)`、そしてWindows標準の `CRLF (0x0D0A)` が入り混じったテキストデータをファイルI/Oに流し込んだ瞬間、VBScriptのパーサーや後続のバッチ処理は沈黙し、あるいは文字化けの魔窟へと誘われる。
安易な `Replace` 関数の連打は、メモリ上で巨大な文字列のコピーを何重にも発生させ、プロセスをメモリ不足(Out of Memory)へと追い込む。
今回は、WSH(Windows Script Host)環境下において、`ADODB.Stream` のバイナリ/テキスト制御能力と `Split` 関数のアルゴリズム的特性を極限まで引き出し、メモリ効率と実行速度を両立させた「改行コード一括クリーンアップ・モジュール」の全貌を解説する。
—
1. 現場の現実:なぜ「Replaceの連打」では破綻するのか
改行コードの正規化において、多くの初学者が陥るアンチパターンが以下のようなコードだ。
‘ 【アンチパターン】絶対にやってはいけない文字列置換の連打
strData = Replace(strData, vbCrLf, vbLf) ‘ いったんLFに統一しようとする
strData = Replace(strData, vbCr, vbLf) ‘ CRをLFに置換
strData = Replace(strData, vbLf, vbCrLf) ‘ 最後にCRLFへ戻す
このアプローチには致命的な欠陥が2つある。
1. メモリの多重消費: VBScriptの文字列型(BSTR)はイミュータブル(不変)に近い挙動を示す。`Replace` を呼ぶたびにヒープ上に新しい文字列領域がアロケーションされ、数MB規模のテキストであれば一瞬でメモリフラグメンテーションを引き起こす。
2. LFとCRLFの二重置換禍: 順番を誤ると、`CRLF` の `LF` 部分だけが置換され、孤立した `CR` や不正な制御コードが生成される。
数万行を超えるログファイルやCSVを扱う基幹バッチにおいて、VBScriptを軽量に保つためには、「ストリームによる安全な読込」 と 「配列処理への昇華」 が不可欠となる。
—
2. アーキテクチャ設計:ADODB.Stream × Split のシナジー
極限環境で耐えうるテキスト処理モジュールは、以下の3つのフェーズで構成する。
1. 文字コード自動判定・読み込み (ADODB.Stream)
BOM(Byte Order Mark)の有無やShift_JIS、UTF-8などの文字エンコーディングを吸収し、確実にメモリ上にテキストを展開する。
2. 正規化スプリット (Split)
環境依存の改行コードを検出し、一時的に一次元配列へバラす。文字列の結合・置換コストを極限まで排除する。
3. 高速ストリーム書き出し
配列を `Join` で一気に結合し、ADODB.Stream経由で一括出力する。
—
3. 実装コード:極限最適化されたクリーンアップ・スクリプト
以下に、実業務の現場へそのまま投入可能なプロダクション・グレードのVBScriptコードを示す。
Option Explicit
‘ ==============================================================================
‘ モジュール名: TextLineNormalizer.vbs
‘ 概要: CRLF / LF / CR が混在するテキストファイルを高速に検出し、CRLFへ統一する
‘ ==============================================================================
Private Sub NormalizeLineEndings(ByVal strSourcePath, ByVal strDestPath, ByVal strCharSet)
Dim objStreamRead, objStreamWrite
Dim strRawData, arrLines, strNormalized
‘ 1. 読み込み用ストリームの初期化
Set objStreamRead = WScript.CreateObject(“ADODB.Stream”)
With objStreamRead
.Type = 2 ‘ adTypeText (テキストデータとして扱う)
.Charset = strCharSet ‘ “UTF-8”, “GB2312”, “Shift_JIS” 等を指定
.Open
.LoadFromFile strSourcePath
strRawData = .ReadText(-1) ‘ adReadAll (-1) で全テキストを一括取得
.Close
End With
Set objStreamRead = Nothing
If Len(strRawData) = 0 Then
WScript.Echo “[WARN] 対象ファイルが空です: ” & strSourcePath
Exit Sub
End If
‘ 2. 混在する改行コードの一次正規化
‘ まず全ての CRLF を LF に置換 (標準的なLFベースの分割準備)
‘ 次に孤立した CR を LF に置換する
strRawData = Replace(strRawData, vbCrLf, vbLf)
strRawData = Replace(strRawData, vbCr, vbLf)
‘ 3. LFをデリミタとして配列に分割 (Splitによるメモリ上の高速展開)
arrLines = Split(strRawData, vbLf)
‘ 生データ変数の参照を即座に解放し、ガベージコレクションを誘導
strRawData = “”
‘ 4. 配列要素の結合 (Joinにより一括で正確な CRLF を付与)
strNormalized = Join(arrLines, vbCrLf)
arrLines = Empty
‘ 5. 書き込み用ストリームの初期化と出力
Set objStreamWrite = WScript.CreateObject(“ADODB.Stream”)
With objStreamWrite
.Type = 2
.Charset = strCharSet
.Open
.WriteText strNormalized, 0 ‘ adWriteChar (テキスト書き込み)
.SaveToFile strDestPath, 2 ‘ adSaveCreateOverWrite (上書き保存)
.Close
End With
Set objStreamWrite = Nothing
WScript.Echo “[SUCCESS] クリーンアップ完了: ” & strDestPath
End Sub
‘ — 実行エントリポイント —
Dim sourceFile, destFile, targetCharset
sourceFile = “C:\Data\mixed_line_endings.csv”
destFile = “C:\Data\normalized_output.csv”
targetCharset = “UTF-8”
‘ 実行時間の計測開始
Dim dblTimer
dblTimer = Timer
Call NormalizeLineEndings(sourceFile, destFile, targetCharset)
WScript.Echo “処理時間: ” & FormatNumber(Timer – dblTimer, 4) & ” 秒”
—
4. チーフアーキテクトが解説するコードの急所
A. オブジェクトの明示的破棄とメモリ管理
VBScriptのCOMコンポーネント(`ADODB.Stream` など)は、スコープを抜けるまでメモリを保持し続ける傾向にある。特に数10MBのテキスト処理では、`Set objStreamRead = Nothing` によって参照カウンタを即座にデクリメントし、OSへのメモリ返却を促すことが極めて重要である。
B. `Replace` の順序と安全地帯
混在テキストのクリーンアップにおける最大の罠は、`vbCrLf` の置換を忘れたまま `vbCr` 単体を置換し、`0x0D0A` の `0x0D` が置換されて `0x0A`(LF)だけが残り、結果として `LF` が二重発生することだ。
本コードでは、最初に `vbCrLf` を `vbLf` へ一網打尽に変換 し、その後に残った孤立した `CR`(レガシーMac由来など)を `vbLf` へ収斂させる。これにより、すべての改行が純粋な `vbLf` に一度プレーン化されるため、後続の `Split(…, vbLf)` が完璧に機能する。
C. ADODB.Stream のエンコーディング耐性
FileSystemObject (FSO) の `OpenTextFile` は、文字コードの自動判別において脆弱であり、特にBOMなしUTF-8やシフトJIS以外のマルチバイト文字が混入した瞬間に文字化けの暴走を起こす。
`ADODB.Stream` を用いることで、インフラ側のロケールに依存しない堅牢なテキストI/Oが担保される。
—
5. レガシー環境・システム間連携における実戦的知見
1. 巨大ファイル(500MB超)へのアプローチ
今回紹介したメモリ展開型(一括読み込み)の手法は、物理メモリの制約上、概ね100MB〜200MB程度のテキストファイルを上限とするべきだ。もしギガバイト級のログを扱う場合は、ストリームを一行ずつ(`ReadLine`)処理するライン・バイ・ライン方式へアーキテクチャを切り替える必要がある(ただし、処理速度はトレードオフとなる)。
2. SJIS環境とUTF-8環境の混在
社内ニッパチシステム(古いVB6やAccess製アプリ)が吐き出すShift_JISと、Web連携で降ってくるUTF-8が混在するフェーズでは、`Charset` プロパティを動的に切り替えるラッパー関数を用意することで、あらゆるレガシーインターフェースの防波堤としてこのスクリプトを機能させることができる。
「動けばいい」という妥協を捨て、メモリの挙動とOSのI/O特性までをコントロール下に置くこと。それこそが、VBScriptを今なお現役の極限ツールたらしめる唯一の道である。
