行単位の処理から脱却せよ:VBScript RegExpで実現する「複数行ブロック」の一括抽出術
こんにちは。自動化の荒波を渡るエンジニアの皆さん。
「マクロの記録」から一歩踏み出し、自分でコードを書き始めた皆さんの成長を、私は心から嬉しく思います。
ここまで、あなたはこう思っていませんでしたか?
「ログファイルからエラーを探すなら、1行ずつ `ReadLine` で読み込んで、`InStr` で特定の文字があるかチェックすれば十分だ」と。
確かに、それは正解の一つです。しかし、もしあなたが扱うログが、「エラーの開始行から、詳細情報が続く数行分までがひとつの塊(ブロック)になっている」ものだったらどうでしょう?
1行ずつループを回し、「エラーが始まったらフラグを立てて、次の行も読み込み、また次の行も……」という処理を書いていたら、コードはすぐに複雑な「スパゲッティ状態」になり、バグの温床となります。
今日は、そんな「行の壁」を軽々と飛び越えるための武器、RegExp(正規表現)オブジェクト、そしてその真髄である`MultiLine` プロパティの活用法を伝授します。ここをクリアすれば、あなたのVBScriptは「単なる自動化ツール」から「高度なデータ解析エンジン」へと進化しますよ。
1. なぜ「行単位」では限界が来るのか?
まず、問題の本質を理解しましょう。
一般的なログファイルには、以下のような構造が含まれることがあります。
[2023-10-01 10:00:01] INFO: System Started
[2023-10-01 10:05:22] ERROR: Database Connection Failed
Reason: Timeout occurred
Trace: line 45 in db_connector.vbs
Code: ERR_001
[2023-10-01 10:10:00] INFO: Heartbeat OK
この「ERROR」から始まる4行分を抜き出したいとき、`ReadLine` によるループ処理では、「今、自分はエラーブロックの中にいるのか?」という「状態(State)」を管理する変数を自前で用意しなければなりません。
- エラー行を見つけたら `isErrorBlock = True` にする。
- 次の行も読み込み続ける。
- 次の `[INFO]` が来たら `isErrorBlock = False` に戻す。
これでは、ログの形式が少し変わるだけでコードが壊れてしまいます。エンジニアとして目指すべきは、「パターン(形)を指定して、一気に抜き出す」という思考への転換です。
2. RegExpオブジェクトの「三種の神器」
VBScriptで正規表現を操るには、`VBScript.RegExp` オブジェクトを使用します。このオブジェクトには、動作を決定づける重要なプロパティがあります。
| プロパティ | 役割 | 現場での意味 |
|---|---|---|
| `Pattern` | 検索する「型」を指定 | 「何を探したいか」の設計図 |
| `Global` | 一致するものをすべて探すか | `True` にしないと、最初に見つかった1件で止まってしまう |
| `MultiLine` | 行の始まり/終わりを認識するか | 今回の主役。`^` や `$` の挙動を変える |
【重要】`MultiLine` プロパティの真実
ここが一番の勘所です。
通常、正規表現の `^`(行頭)や `$`(行末)は、「テキスト全体の最初」と「テキスト全体の最後」にしか反応しません。
しかし、`MultiLine = True` に設定した瞬間、これらの記号は「改行の直後」や「改行の直前」にも反応するようになります。つまり、テキスト全体を一つの巨大な塊として扱いながら、その内部にある「各行の境界」を意識して検索できるようになるのです。
3. 実践:エラーブロックを一括抽出するコード
では、実際に使えるコードを見てみましょう。
「`[ERROR]` で始まり、次の `[` で始まる行の手前までを抜き出す」というロジックを実装します。
' =================================================================
' ログファイル・セクション抽出スクリプト
' 目的: [ERROR] から始まる複数行のブロックを正規表現で一括抽出する
' =================================================================
Option Explicit
Dim objFSO, objFile, strLogContent
Dim objRegExp, objMatches, objMatch
Dim strTargetPattern
' --- 1. 準備工程 ---
Set objFSO = CreateObject("Scripting.FileSystemObject")
' テスト用のログファイルを作成(本来は既存のファイルパスを指定)
CreateDummyLog "test_log.txt"
' ファイルの全内容をメモリ上に読み込む
' ※注意: 数GBあるような巨大ファイルの場合、ReadAllはメモリ不足を招くため
' 分割読み込みなどの工夫が必要ですが、通常のログならこれで十分です。
Set objFile = objFSO.OpenTextFile("test_log.txt", 1)
strLogContent = objFile.ReadAll
objFile.Close
' --- 2. 正規表現の構築 ---
Set objRegExp = CreateObject("VBScript.RegExp")
With objRegExp
' パターンの解説:
' ^\[ERROR\] : 行頭が "[ERROR]" で始まる
' ([\s\S]*?) : 任意の文字(改行含む)を、最短一致で繰り返す
' (?=\n\[|\z) : 先読み(Lookahead)。「改行+[」または「文字列の末尾」が来る手前まで
.Pattern = "^\[ERROR\].*?([\s\S]*?)(?=\n\[|\z)"
.Global = True ' 全ての該当箇所を見つける
.IgnoreCase = True ' 大文字小文字を区別しない
.MultiLine = True ' 行頭(^)の判定に改行を考慮させる ★ここが肝!
End With
' --- 3. 実行と結果表示 ---
Set objMatches = objRegExp.Execute(strLogContent)
WScript.Echo "--- 抽出結果 ---"
If objMatches.Count = 0 Then
WScript.Echo "エラーブロックは見つかりませんでした。"
Else
For Each objMatch In objMatches
' objMatch.Value にはマッチしたブロック全体が入っている
WScript.Echo "--------------------------"
WScript.Echo objMatch.Value
Next
End If
' --- 4. 後片付け (オブジェクトのライフサイクル管理) ---
' 使い終わったオブジェクトは明示的に解放するのがプロの作法です
Set objRegExp = Nothing
Set objMatches = Nothing
Set objFSO = Nothing
' -----------------------------------------------------------------
' サポート関数: テスト用ログの生成
' -----------------------------------------------------------------
Sub CreateDummyLog(filePath)
Dim f
Set f = objFSO.CreateTextFile(filePath, True)
f.WriteLine "[2023-10-01 10:00:01] INFO: System Started"
f.WriteLine "[2023-10-01 10:05:22] ERROR: Database Connection Failed"
f.WriteLine " Reason: Timeout occurred"
f.WriteLine " Trace: line 45 in db_connector.vbs"
f.WriteLine " Code: ERR_001"
f.WriteLine "[2023-10-01 10:10:00] INFO: Heartbeat OK"
f.WriteLine "[2023-10-01 10:15:00] ERROR: Disk Full"
f.WriteLine " Reason: /var/log is 100% full"
f.WriteLine "[2023-10-01 10:20:00] INFO: System Shutdown"
f.Close
End Sub
4. 陥りやすい罠と、プロの回避策
この技術を使いこなすために、以下の2点には細心の注意を払ってください。
① メモリの「重み」を知る
上記のコードでは `objFile.ReadAll` を使ってファイルの中身を丸ごと変数 `strLogContent` に格納しています。これは非常に高速ですが、「数GBある巨大なログファイル」に対して行うと、VBScriptがメモリ不足でクラッシュします。
- 対策: 巨大なファイルの場合は、正規表現で一気に抜くのではなく、`ReadLine` で読み込みつつ、正規表現を「行単位の判定」に限定して使う(つまり `MultiLine` を使わずに、各行がパターンに合致するかをチェックする)という、ハイブリッドなアプローチを検討してください。
② 「最短一致」と「最長一致」の使い分け
正規表現でよく使う `*`(量指定子)には、欲張りな性質があります。
例えば `\[ERROR\].*\[INFO\]` と書くと、最初のERRORから、ファイル内の最後に出てくるINFOまでを一つの塊として繋げて抽出してしまうことがあります(これを「最長一致」と呼びます)。
- 対策: 今回のコードのように `*?` (後ろに `?` をつける)を使い、「最短一致」を指定することで、「次の条件に合う場所が見つかったら、すぐに抽出を止める」という制御が可能になります。
まとめ:ここをクリアすれば、基本はバッチリです
「1行ずつループして、フラグを管理する」という泥臭い処理から抜け出し、「パターンを定義して、一気に抜き出す」という抽象度の高い処理へ。
これができるようになると、コードは劇的に短くなり、読みやすくなり、メンテナンス性が向上します。正規表現は一見、暗号のように見えますが、その仕組み(特に `MultiLine` の挙動)を理解してしまえば、これほど強力な武器はありません。
一歩ずつ、確実に。
次にあなたが書くコードが、より洗練されたものになることを期待していますよ。頑張ってくださいね!
