限界突破の設計思想:VBScriptによる「大量ファイル並列処理ディスパッチャー」の実装術
こんにちは。自動化の荒野を歩むエンジニアの皆さん。
日々、業務の自動化に励んでいることと思います。マクロの記録から一歩踏み出し、自分でコードを書き始めた皆さんは、今まさに「プログラミングの入り口」に立っています。
しかし、ある時、あなたは「壁」にぶつかるはずです。
「数万個のファイルを処理しようとしたら、スクリプトが終わるまでに昼休みが終わってしまった……」という壁です。
VBScriptは非常に強力で、Windowsのあらゆる操作を操れる素晴らしい言語です。しかし、一つだけ大きな弱点があります。それは「シングルスレッド(単一の作業員)」であること。つまり、一つのスクリプトは、一つの作業を順番にしかこなせません。
今日は、その壁を打ち破るための、伝説的な設計パターン「ディスパッチャー(配分者)モデル」を伝授します。これを知れば、あなたは単なる「スクリプト使い」から、システムを設計する「アーキテクト」へと進化できるはずですよ。
1. なぜ「並列処理」が必要なのか?(概念図)
通常、VBScriptでファイルを処理する場合、以下のような「一列に並んだ行列」になります。
【通常(シングルスレッド)の動き】
`[ファイル1] → [ファイル2] → [ファイル3] → … → [ファイル10000]`
これでは、一つ一つの処理が遅ければ、全体の時間は膨大になります。
【ディスパッチャー(マルチプロセス)の動き】
ここで、「ディスパッチャー(司令塔)」という役割のスクリプトを導入します。司令塔は、仕事を細かく分割し、複数の「ワーカー(作業員)」へ同時に命令を出します。
[ 司令塔 (Dispatcher.vbs) ]
│
├─▶ [ 作業員A (cscript.exe Worker.vbs) ] ──▶ [ ファイル1〜2500 ]
├─▶ [ 作業員B (cscript.exe Worker.vbs) ] ──▶ [ ファイル2501〜5000 ]
├─▶ [ 作業員C (cscript.exe Worker.vbs) ] ──▶ [ ファイル5001〜7500 ]
└─▶ [ 作業員D (cscript.exe Worker.vbs) ] ──▶ [ ファイル7501〜10000 ]
このように、OSの機能(`cscript.exe`)を複数立ち上げることで、実質的に「複数の作業員が同時に動いている状態」を作り出すのです。これが並列処理の正体です。
2. 実践:並列処理システムの構築
それでは、実際に動くコードを見ていきましょう。
今回は、「特定のフォルダ内のファイルを、分割して一斉にリネーム(または移動)する」という仕組みを作ります。
プロフェッショナルの知恵として、コマンドライン引数に直接ファイル名を渡すと「文字数制限」に引っかかるため、「処理対象リストを一時ファイルに書き出し、それをワーカーに読み込ませる」という堅牢な設計を採用します。
A. 司令塔:`Dispatcher.vbs`
このスクリプトは、ファイルの一覧を取得し、グループ分けしてワーカーを起動します。
' ===========================================================================
' Dispatcher.vbs
' 役割: ファイルを分割し、複数のWorkerプロセスを起動する司令塔
' ===========================================================================
Option Explicit
Dim fso, shell, targetFolder, workerPath, workerCount
Dim allFiles, fileCount, chunkSize, i, workerIdx
Dim listFile, cmdLine
Set fso = CreateObject("Scripting.FileSystemObject")
Set shell = CreateObject("WScript.Shell")
' --- 設定エリア ---
targetFolder = "C:\Test\TargetFiles" ' 処理したいファイルが入ったフォルダ
workerPath = "C:\Test\Worker.vbs" ' 呼び出すワーカーのパス
workerCount = 4 ' 同時に動かす作業員の数(CPUコア数に合わせると効率的)
' ------------------
If Not fso.FolderExists(targetFolder) Then
WScript.Echo "エラー: 対象フォルダが見つかりません。"
WScript.Quit
End If
' 1. 全ファイルを取得
Set allFiles = fso.GetFolder(targetFolder).Files
fileCount = allFiles.Count
If fileCount = 0 Then
WScript.Echo "処理対象のファイルがありません。"
WScript.Quit
End If
WScript.Echo "総ファイル数: " & fileCount & " 件"
WScript.Echo workerCount & " 個のプロセスで並列処理を開始します..."
' 2. 分割処理の計算
chunkSize = Int(fileCount / workerCount)
If chunkSize = 0 Then chunkSize = fileCount
' 3. ワーカーの起動ループ
For i = 0 To workerCount - 1
' 処理範囲の計算
Dim startIdx, endIdx
startIdx = (i * chunkSize) + 1
endIdx = (i + 1) * chunkSize
If i = workerCount - 1 Then endIdx = fileCount ' 最後のワーカーは端数すべてを担当
' 作業員用の「指示書(一時ファイル)」を作成
listFile = "C:\Test\worker_list_" & i & ".txt"
Call CreateInstructionList(targetFolder, listFile, startIdx, endIdx)
' 4. ワーカーを非同期(バックグラウンド)で起動
' Run メソッドの第3引数を False にすることで、終了を待たずに次のループへ進みます(並列化のキモ!)
cmdLine = "cscript.exe //nologo """ & workerPath & """ """ & listFile & """"
shell.Run cmdLine, 1, False
WScript.Echo "Worker " & i & " を起動しました (対象: " & startIdx & "~" & endIdx & ")"
Next
WScript.Echo "すべてのワーカーに指示を出しました。完了を待機してください。"
' --- サブルーチン: 指示書作成 ---
Sub CreateInstructionList(folderPath, outPath, sIdx, eIdx)
Dim f, currentIdx, fileItem
Set f = fso.CreateTextFile(outPath, True)
currentIdx = 1
For Each fileItem In fso.GetFolder(folderPath).Files
If currentIdx >= sIdx And currentIdx <= eIdx Then
f.WriteLine fileItem.Path
End If
currentIdx = currentIdx + 1
If currentIdx > eIdx Then Exit For
Next
f.Close
End Sub
B. 作業員:`Worker.vbs`
このスクリプトは、司令塔から渡された「指示書」を読み込み、黙々と作業をこなします。
' ===========================================================================
' Worker.vbs
' 役割: 指示書(テキストファイル)を読み込み、実際のファイル操作を行う
' ===========================================================================
Option Explicit
Dim fso, args, listFile, f, filePath
Set fso = CreateObject("Scripting.FileSystemObject")
Set args = WScript.Arguments
' 1. 指示書のパスを受け取る
If args.Count = 0 Then
WScript.Quit
End If
listFile = args(0)
' 2. 指示書を一行ずつ読み込んで処理
If fso.FileExists(listFile) Then
Set f = fso.OpenTextFile(listFile, 1) ' 読み取りモード
Do Until f.AtEndOfStream
filePath = f.ReadLine
If fso.FileExists(filePath) Then
' --- ここに実際の処理を書く ---
' 例として、ファイル名の先頭に "[PROCESSED_" を付けるリネーム処理
On Error Resume Next ' エラーが起きても止まらないように(重要!)
Dim newName
newName = fso.GetParentFolderName(filePath) & "\PROCESSED_" & fso.GetFileName(filePath)
fso.MoveFile filePath, newName
If Err.Number <> 0 Then
' エラーログの出力(本来はログファイルに書き出すのがプロ)
' WScript.Echo "Error processing: " & filePath
Err.Clear
End If
On Error GoTo 0
' ----------------------------
End If
Loop
f.Close
' 3. 終わったら指示書を削除して自分を片付ける
fso.DeleteFile listFile
End If
3. プロフェッショナルの視点:陥りやすい罠と回避策
ここまで読んで「よし、動かしてみよう!」と思ったあなた。素晴らしい!
しかし、実戦ではいくつかの「落とし穴」が待ち構えています。ここをクリアすれば、あなたはもう初心者ではありません。
① ファイル・ロック(競合)の恐怖
複数のワーカーが「同じファイル」を同時に触ろうとすると、エラーが発生します。
- 対策: 今回の設計のように、司令塔が「担当範囲」を明確に分けて指示書を作ることで、ワーカー同士が同じファイルに触れないように制御(排他制御)するのが鉄則です。
② コマンドラインの長さ制限
`shell.Run “cscript.exe worker.vbs file1.txt file2.txt …”` のように、ファイル名を直接引数に渡すと、Windowsの仕様で文字数制限(約8191文字)に当たり、途中で切れてしまいます。
- 対策: 今回の実装で行ったように、「ファイルリストをテキストファイルに書き出し、そのパスだけを渡す」手法が最も安全でプロフェッショナルなやり方です。
③ エラーハンドリングの重要性
並列処理では、一つのワーカーがエラーで止まっても、他のワーカーは走り続けます。
- 対策: `On Error Resume Next` を適切に使い、エラーが起きても「どのファイルで失敗したか」をログに残す仕組みを組み込みましょう。止まってしまうスクリプトは、自動化において「使い物にならない」とみなされます。
④ リソースの使いすぎ
`workerCount` を 100 とかに設定すれば、さらに速くなる……と思ったら大間違いです。
- 対策: CPUのコア数やディスクのI/O性能(読み書き速度)を超えてワーカーを増やすと、逆に「作業員同士がぶつかり合って(コンテキストスイッチ)」遅くなります。まずは CPUのコア数と同じか、その2倍程度から試すのがスマートですよ。
まとめ:次の一歩へ
いかがでしたか?
単なる「ファイルの移動」という単純なタスクも、「司令塔」と「作業員」に役割を分けるという視点を持つだけで、圧倒的なパフォーマンスを発揮する「システム」へと昇華されます。
VBScriptは古い言語だと言われることもありますが、その仕組みを深く理解し、こうして設計思想を応用することで、現代の業務にも十分に通用する強力な武器になります。
「動けばいい」から「効率よく、堅牢に動く」へ。
このステップアップを楽しみながら、ぜひ実装してみてくださいね。応援していますよ!
