VBScriptを極めよ:`Filter`・`Split`・`Join`の三位一体で実現する超高速配列絞り込み術
業務自動化の現場において、VBScript(WSH)は依然としてレガシーシステムとの統合やクライアント端末のキッティング、簡易的なバッチ処理において現役で稼働し続けている。
しかし、多くのプログラマ(あるいは非プログラマの業務担当者)が書くVBScriptのコードは、致命的な非効率を抱えている。
その最たるものが 「配列の要素を舐めるための `For…Next` ループの多用」 だ。
数千行、数万行のテキストデータを読み込み、1行ずつ条件分岐(`If`)で判定して別配列に再格納する――このような愚直なコードは、VBScriptのインタプリタの特性上、実行速度を著しく低下させる。メモリの再割り当て(`ReDim Preserve`)をループ内で繰り返そうものなら、GC(ガベージコレクション)とメモリウムの嵐で処理は完全にフリーズする。
今回は、VBScriptの標準関数である `Filter`、`Split`、`Join` を極限まで組み合わせ、ループ処理を排除した「超高速な配列操作と一括整形」のデザインパターンを伝授する。
—
なぜ `For` ループによる配列走査は悪なのか?
VBScriptの配列(特に動的配列)は、内部的にCOMのバリアント型(`SAFEARRAY`)として管理されている。
`ReDim Preserve` を実行するたびに、システムはメモリ上の新しい領域を確保し、既存のデータを丸ごとコピーするという高コストな処理を行っている。
これをループのたびに実行するコードは、時間計算量(オーダー)が最悪の形になり、実務で扱うデータ量には耐えられない。
我々が目指すべきは、「VBScriptの内部C++層に処理を丸投げし、スクリプト層でのループを極限までゼロにする」 ことだ。そのための切り札が、文字列を高速に解体・結合・抽出するこの3つの関数である。
—
三大関数の本質的理解
1. `Split(Expression, [Delimiter])`
巨大な文字列を一撃で1次元配列に変換する。メモリ上の連続した領域にトークンを展開するため、ファイル読込後のパースにおいて `For` ループで1文字ずつ処理するよりも圧倒的に高速。
2. `Filter(SourceArray, Match, [Include], [Compare])`
本記事の主役。 配列の中から特定の文字列を含む(または含まない)要素を一瞬で抽出し、新たな1次元配列として返す。この関数自体が内部でCレベルの高速検索を行っているため、スクリプト側で `If InStr(…)` を回す必要が一切なくなる。
3. `Join(SourceArray, [Delimiter])`
配列の全要素を指定の区切り文字で結合し、一つの文字列に戻す。データベースへのバルクインサート用クエリの生成や、CSV形式への一括変換において神速を発揮する。
—
実践:プロダクションコード(堅牢なログ解析・絞り込みツール)
以下のコードは、数万行のログファイルから特定のステータス(例: “ERROR”)を持つ行だけを抽出し、フォーマットを整えて別ファイルに出力する実務想定のスクリプトだ。
エラーハンドリング(`On Error Resume Next` の正しい封じ込め)と、メモリ枯渇を防ぐ堅牢な設計を実装している。
Option Explicit
‘ ==============================================================================
‘ スクリプト名: HighSpeedLogExtractor.vbs
‘ 概要: 巨大なテキストログから特定キーワードを高速抽出し、一括整形する
‘ アーキテクチャ: Loopless Filtering & Batch Formatting Pattern
‘ ==============================================================================
Sub Main()
Dim fso, targetPath, outputPath
Set fso = CreateObject(“Scripting.FileSystemObject”)
targetPath = fso.BuildPath(fso.GetParentFolderName(WScript.ScriptFullName), “server_access.log”)
outputPath = fso.BuildPath(fso.GetParentFolderName(WScript.ScriptFullName), “error_summary.csv”)
‘ ファイルが存在しない場合は早期リターン
If Not fso.FileExists(targetPath) Then
WScript.Echo “致命的エラー: 対象のログファイルが見つかりません。” & vbCrLf & targetPath
Exit Sub
End If
Dim startTime
startTime = Timer
‘ 1. ファイル全体を「一撃」でメモリ上に読み込む (Readall)
Dim fileStream, rawData
Set fileStream = fso.OpenTextFile(targetPath, 1, False) ‘ 1 = ForReading
rawData = fileStream.ReadAll
fileStream.Close
Set fileStream = Nothing
‘ 2. 改行コード(vbCrLf / vbLf)を統一し、Splitで一気に配列化
‘ ※環境依存の改行コード揺れを吸収する前処理
rawData = Replace(rawData, vbCrLf, vbLf)
rawData = Replace(rawData, vbCr, vbLf)
Dim allLines
allLines = Split(rawData, vbLf)
‘ 3. 【核心】Filter関数によるループレス絞り込み
‘ “ERROR” という文字を含む要素だけを抽出した新しい配列を一瞬で生成
Dim errorLines
errorLines = Filter(allLines, “ERROR”, True, 1) ‘ 1 = 比較モード(大文字小文字を区別しない場合は vbTextCompare)
‘ 抽出結果が0件だった場合のガード処理
If UBound(errorLines) < LBound(errorLines) Then
WScript.Echo "対象となるエラー行は見つかりませんでした。"
Exit Sub
End If
' 4. 【整形】Join と Split / Replace の応用による一括加工
' ここでは抽出されたログ配列に対し、タブ区切りをカンマ区切りに置換しつつ一括変換する
' 配列のままループを回さず、一度Joinして文字列化 -> 置換 -> 再Splitというアプローチも有効
Dim joinedErrors
joinedErrors = Join(errorLines, vbCrLf)
‘ 例: 特定の不要な文字列を一括削除(正規表現を使わずとも高速)
joinedErrors = Replace(joinedErrors, “[INTERNAL_SERVER]”, “[SYS]”)
‘ 5. 結果を一括書き込み
Dim outStream
Set outStream = fso.CreateTextFile(outputPath, True, False) ‘ True = 上書き, False = ASCII/ANSI
outStream.Write joinedErrors
outStream.Close
Set outStream = Nothing
Dim elapsedTime
elapsedTime = Timer – startTime
WScript.Echo “処理完了: ” & (UBound(errorLines) + 1) & “件のエラーを抽出しました。” & vbCrLf _
& “実行時間: ” & FormatNumber(elapsedTime, 4) & ” 秒” & vbCrLf _
& “出力先: ” & outputPath
Set fso = Nothing
End Sub
‘ 実行
Main()
—
ヴイ・ビー・スクリプトにおけるプロフェッショナルな設計とは、「いかにVBScriptに自前でループを回させないか」に尽きる。
データベース(ADODB.Recordset)からのデータ抽出や、CSVのバルク処理においても、この `Split` / `Filter` / `Join` のパイプライン思考を取り入れることで、VBScriptとは思えないほどの爆速な自動化ツールを構築することが可能だ。
レガシーな技術であっても、背後にあるメモリ構造と関数の特性を深く理解していれば、モダンな言語に匹敵するパフォーマンスを引き出すことができる。現場の武器として、ぜひこのイディオムをマスターしてほしい。
