【実務・中級編】【実務中級者向け】FSOで読み込んだテキスト内の「無駄な連続空行」を単一空行へ整形・圧縮するテキストクレンジング – VBScript (Visual Basic Scripting Edition)解析バイブル

スポンサーリンク
スポンサーリンク

凡庸なスクリプトを、プロフェッショナルなツールへ昇華させる:FSOと正規表現による「テキストクレンジング」の極意

現場の自動化担当者諸君。
君たちが書いた「とりあえず動くスクリプト」が、本番環境のサーバーでエラーを吐き、あるいは数ギガバイトのログファイルを読み込んでシステムをフリーズさせたとき、初めて「設計」の重みを知ることになる。

今回扱うテーマは、実務で頻出する「テキスト内の無駄な連続空行を、単一の空行に集約する」という、一見単純に見えて実は奥が深いクレンジング処理だ。

「`Replace` 関数をループさせればいいじゃないか」と考えているなら、今すぐその思考を捨ててほしい。それは、穴の空いたバケツで水を汲むような、非効率かつ脆弱な設計だ。

本稿では、単なるコードの提示ではなく、「なぜその設計が必要なのか」、そして「いかにして堅牢なプロダクションコードを構築するか」という、アーキテクトの視点を伝授する。

1. 初級者が陥る「Replaceループ」の罠

多くの初学者は、連続する空行を消去するために、以下のようなロジックを書きがちだ。

' --- 避けるべき非効率なアプローチ ---
Do While InStr(content, vbCrLf & vbCrLf & vbCrLf) > 0
    content = Replace(content, vbCrLf & vbCrLf & vbCrLf, vbCrLf & vbCrLf)
Loop

なぜこれが「プロの仕事」ではないのか? 理由は3つある。

1. 計算量の爆発 (Complexity): 連続する空行が非常に多い場合、文字列の走査と再生成を何度も繰り返すことになり、O(n^2)に近い計算コストが発生する。
2. 改行コードの不一致: `vbCrLf` (Windows) だけを想定していると、Unix形式の `LF` が混在した瞬間にこのロジックは沈黙する。ログファイルの世界では、改行コードの混在は「日常」だ。
3. メモリの無駄遣い: 文字列操作のたびに新しいメモリ領域が確保され、ガベージコレクション(あるいはVBScriptのメモリ管理)に過度な負荷をかける。

我々が目指すべきは、「一度の走査(Single Pass)で、パターンを検知し、置換する」設計だ。

2. アーキテクチャの選定:RegExp と ADODB.Stream

この課題に対する最適解は、Regular Expressions (正規表現) の採用だ。正規表現エンジンは、内部的に高度に最適化された有限オートマトンを用いてパターンマッチングを行う。これにより、複雑な条件も高速に処理できる。

また、FSO(FileSystemObject)には致命的な弱点がある。それは「UTF-8への対応が極めて貧弱である」という点だ。現代のシステムログの多くはUTF-8である。FSOだけで無理に戦おうとすれば、文字化けという名の地獄を見ることになる。

そこで、我々は以下のコンポーネントを組み合わせる。

  • `ADODB.Stream`: エンコーディング(UTF-8/ANSI)を制御し、安全にファイルを読み書きするため。
  • `VBScript.RegExp`: 連続する改行パターンを、単一の正規表現で一括捕捉するため。

3. プロダクション・レディな実装例

以下に、実務でそのまま利用可能な、堅牢性を重視したコードを示す。

' =============================================================================
' Module: TextCleanser
' Description: テキスト内の連続する空行を単一の空行に圧縮する
' Author: Chief Architect
' =============================================================================

Option Explicit

' メイン実行ルーチン
' 使用法: cscript //nologo TextCleanser.vbs "input.log" "output.log"
Main

Sub Main()
    Dim strInputPath, strOutputPath
    
    ' 引数チェック (実務ではここが命運を分ける)
    If WScript.Arguments.Count < 2 Then
        WScript.Echo "Usage: cscript //nologo " & WScript.ScriptName & " <input_file> <output_file>"
        WScript.Quit 1
    End If

    strInputPath  = WScript.Arguments(0)
    strOutputPath = WScript.Arguments(1)

    ' プロセス実行
    If CleanseTextFile(strInputPath, strOutputPath) Then
        WScript.Echo "SUCCESS: Text cleansing completed."
    Else
        WScript.Echo "ERROR: Process failed. Check error details."
        WScript.Quit 1
    End If
End Sub

''' <summary>
''' 指定されたファイルを読み込み、連続空行を圧縮して書き出す
''' </summary>
Function CleanseTextFile(ByVal strIn, ByVal strOut)
    On Error Resume Next ' エラーハンドリングの開始
    
    Dim objStreamIn, objStreamOut, objRegExp
    Dim strContent, strResult
    Dim bSuccess
    
    bSuccess = False

    ' 1. 正規表現オブジェクトの初期化
    Set objRegExp = CreateObject("VBScript.RegExp")
    With objRegExp
        .Pattern = "(\r\n|\r|\n){3,}" ' 3つ以上の改行(=2行以上の空行)にマッチ
        .Global = True                ' 全箇所を対象
        .MultiLine = True
    End With

    ' 2. 入力ストリームのオープン (ADODB.Streamを使用しUTF-8をサポート)
    Set objStreamIn = CreateObject("ADODB.Stream")
    objStreamIn.Type = 2 ' adTypeText
    objStreamIn.Charset = "UTF-8"
    objStreamIn.Open
    
    ' ファイルの存在確認(FSOの代わりだが、ここではストリームのオープン失敗で判定)
    objStreamIn.LoadFromFile strIn
    If Err.Number <> 0 Then
        WScript.Echo "Error loading file: " & Err.Description
        GoTo CleanUp
    End If

    ' 3. 全内容の読み込みと置換処理
    strContent = objStreamIn.ReadText
    
    ' 改行コードをWindows標準(CRLF)に正規化しつつ、連続空行を圧縮する
    ' まず全ての改行をCRLFに統一し、その後に連続CRLFを圧縮する二段構えが最も堅牢
    strContent = Replace(strContent, vbCrLf, vbLf)
    strContent = Replace(strContent, vbCr, vbLf)
    strContent = Replace(strContent, vbLf, vbCrLf) ' 統一
    
    ' 正規表現による圧縮 (3つ以上の改行を2つの改行[=1行の空行]へ)
    ' ※ パターンを「(\r\n){3,}」とすることで、空行がいくつあっても「1行の空行」にする
    strResult = objRegExp.Replace(strContent, vbCrLf & vbCrLf)

    ' 4. 出力ストリームの書き込み
    Set objStreamOut = CreateObject("ADODB.Stream")
    objStreamOut.Type = 2 ' adTypeText
    objStreamOut.Charset = "UTF-8"
    objStreamOut.Open
    objStreamOut.WriteText strResult
    objStreamOut.SaveToFile strOut, 2 ' adSaveCreateOverWrite
    
    If Err.Number = 0 Then
        bSuccess = True
    Else
        WScript.Echo "Error writing file: " & Err.Description
    End If

CleanUp:
    ' 5. オブジェクトのライフサイクル管理 (明示的な破棄)
    ' これを怠ると、ファイルがロックされたままになる
    If Not objStreamIn Is Nothing Then objStreamIn.Close : Set objStreamIn = Nothing
    If Not objStreamOut Is Nothing Then objStreamOut.Close : Set objStreamOut = Nothing
    Set objRegExp = Nothing
    
    On Error GoTo 0
    CleanseTextFile = bSuccess
End Function

4. アーキテクトの視点:設計の勘所

このコードが、単なる「動くコード」と何が違うのか。その設計思想を解説する。

A. エンコーディングの抽象化

`ADODB.Stream` を使用している点は、実務において極めて重要だ。`FileSystemObject` は古い。`FileSystemObject.OpenTextFile` は、UTF-8のBOM(Byte Order Mark)の扱いや、BOMなしUTF-8において脆弱だ。`ADODB.Stream` を使うことで、将来的に「Shift-JISのログも扱いたい」となった際も、`Charset` プロパティを変更するだけで対応できる。

B. 改行コードの正規化戦略

正規表現で `(\r\n|\r|\n){3,}` を使う手法は賢明だが、入力ファイルが `LF` だけの場合に `vbCrLf` で置換してしまうと、結果として改行コードが混在する。
私の実装では、「一度全ての改行を統一(Normalize)してから、正規表現を適用する」という二段階プロセスを踏んでいる。これにより、出力ファイルの整合性を保証している。

C. リソースの確実な解放

`CleanUp` セクションによるオブジェクトの明示的な破棄は、業務自動化において「作法」ではない。「必須条件」だ。
特に、ループ内でこの処理を行うようなバッチ処理の場合、`Stream` オブジェクトを閉じ忘れると、OSのファイルハンドルを使い果たし、システム全体のパフォーマンスを低下させる。

D. エラーハンドリングの設計

`On Error Resume Next` を使いつつも、直後に `Err.Number` をチェックして、エラーが発生した場合は即座に `CleanUp` へジャンプさせる。これは「エラーを無視する」のではなく、「エラーを検知して、安全に後片付けをしてから終了する」ための設計だ。

結論

自動化エンジニアとしての格の違いは、「正常系が動くこと」ではなく、「異常系に対してどれだけ優雅に振る舞えるか」で決まる。

今回紹介した、正規表現とストリーム制御を用いた設計は、パフォーマンス、保守性、堅牢性のすべてにおいて、実務レベルの要求に応えうるものだ。このコードをベースに、君たちのプロジェクトに最適な拡張(ログのフィルタリングや、DBへの書き出しなど)を加えてほしい。

健闘を祈る。

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