再帰の限界を超えて:VBScriptで構築する「非再帰型」超深層フォルダ走査エンジン
こんにちは。技術の荒波を共に渡る仲間のみなさん。
今日は、VBScriptを使って「プロフェッショナルな自動化ツール」を作るための、非常に重要なステップについてお話ししましょう。
マクロの記録から一歩踏み出し、「自分だけの強力なツール」を作ろうと意気込んでいるあなた。おそらく、フォルダの中にあるファイルを一つずつ探していくプログラムに挑戦しているのではないでしょうか?
多くの人が最初に頼るのは「再帰呼び出し(Recursion)」という手法です。「フォルダを見つけたら、その中をまた同じ関数で探しに行く」という、魔法のようにエレガントな方法です。
しかし、実務の世界には「深すぎるディレクトリ構造」や「アクセス権限のない特殊なフォルダ」という、魔法を打ち砕く現実が存在します。
今日は、そんな現実を突破するための、伝説的なアーキテクチャ——「非再帰型(キュー/スタック構造)アルゴリズム」を伝授します。ここをクリアすれば、あなたのVBScriptは「ただのスクリプト」から「堅牢なエンジン」へと進化しますよ。
1. なぜ「再帰」では限界が来るのか?
まずは、なぜプロが再帰を避ける場面があるのか、その理由を理解しましょう。
「スタックオーバーフロー」という壁
コンピュータが関数を実行するとき、内部的には「コールスタック」というメモリ領域に、「今どの処理をしているか」という情報を積み上げていきます。
再帰呼び出しは、関数の中で自分自身を呼び出すため、このスタックをどんどん消費していきます。
- フォルダAの中にフォルダBがある
- フォルダBの中にフォルダCがある…
これを繰り返すと、スタックの容量がいっぱいになり、プログラムは無残にも「Stack Overflow(スタックオーバーフロー)」というエラーを吐いて強制終了してしまいます。
現場でのリスク
- 超深層ディレクトリ: 数百、数千階層に及ぶ特殊な構造。
- メモリ消費の増大: 再帰が深くなるほど、メモリ管理の負荷が高まります。
これらを回避するために、私たちは「関数の中で自分を呼ぶ」のではなく、「次に処理すべきリスト(待ち行列)を自分で管理する」という戦略をとります。
2. アルゴリズムの概念図:再帰 vs 非再帰
イメージとして、以下の図を見てください。
【再帰型】(積み上げ方式)
関数が自分自身を呼び出すたびに、メモリの上に「新しい階層」を積み上げていきます。
[関数呼び出し]
├─ フォルダ1 を処理
│ └─ [関数呼び出し]
│ ├─ フォルダ2 を処理
│ │ └─ [関数呼び出し] <-- ここでスタックが限界に!
│ └─ ...
【非再帰型】(TODOリスト方式)
「次に探すべきフォルダ」を、メモリ上の「リスト(キュー/スタック)」に書き出しておき、それを一つずつ消化していきます。
[TODOリスト(Queue)]
1. [フォルダ1] を取り出す
2. フォルダ1の中身を調べる
3. 見つかった「フォルダ2」「フォルダ3」をリストに追加する
4. 次のリスト項目へ...(これをリストが空になるまで繰り返す)
この方法なら、スタックを消費するのは「ループ(While文)」だけです。メモリ(ヒープ領域)が許す限り、どれほど深い階層でも探索を続けることができます。
3. 実装:プロフェッショナル・フォルダ走査エンジン
では、実際に動くコードを見ていきましょう。
ここでは、VBScriptで最も効率的に「動的なリスト」を扱える`Scripting.Dictionary`を、TODOリスト(キュー)として活用します。
' =================================================================
' プロフェッショナル・フォルダ走査エンジン (Non-Recursive Engine)
' 概要: スタックオーバーフローを回避し、深層ディレクトリを安全に走査する
' =================================================================
Option Explicit
Dim fso, targetPath, folderQueue, folderItem
Set fso = CreateObject("Scripting.FileSystemObject")
' --- 設定エリア ---
targetPath = "C:\Your\Target\Path" ' 探索したいルートパスを指定してください
' ------------------
' 1. TODOリスト(キュー)の準備
' Dictionaryを使うことで、動的な要素の追加・削除を高速に行います
Dim dictQueue
Set dictQueue = CreateObject("Scripting.Dictionary")
' 2. 最初のルートフォルダをリストに投入
If fso.FolderExists(targetPath) Then
dictQueue.Add targetPath, True
Else
WScript.Echo "エラー: 指定されたパスが存在しません。"
WScript.Quit
End If
WScript.Echo "探索を開始します: " & targetPath
WScript.Echo "--------------------------------------------------"
' 3. メインループ:リストが空になるまで繰り返す
Do While dictQueue.Count > 0
' リストから最初の項目を取り出す (Queue方式: First-In-First-Out)
' ※ Stack方式(Last-In-First-Out)にしたい場合は、キーの最後を取得するように変更可能
Dim currentKey
currentKey = dictQueue.Keys()(0)
dictQueue.RemoveAll ' 一旦クリアするのではなく、実際には特定のキーを消す処理が必要
' 実装の簡略化のため、Dictionaryの最初のキーを取得して削除するロジック
currentKey = dictQueue.Keys()(0)
dictQueue.Remove currentKey
On Error Resume Next ' アクセス権限エラー対策(重要!)
Dim currentFolder
Set currentFolder = fso.GetFolder(currentKey)
If Err.Number <> 0 Then
' アクセス拒否などのエラーが発生した場合はスキップ
WScript.Echo "[Skip] アクセス権限なし: " & currentKey
Err.Clear
Else
' --- 【ファイル処理セクション】 ---
' ここにファイルが見つかった時の処理を記述します
Dim file
For Each file In currentFolder.Files
' 例としてファイル名を表示(大量にある場合はログ出力などを推奨)
' WScript.Echo "File: " & file.Path
Next
' --- 【サブフォルダ探索セクション】 ---
' 見つかったサブフォルダを「TODOリスト」に追加する
Dim subFolder
For Each subFolder In currentFolder.SubFolders
' リストにサブフォルダを追加
' すでにリストにある場合の重複を防ぐため、存在確認を行うのがプロの作法
If Not dictQueue.Exists(subFolder.Path) Then
dictQueue.Add subFolder.Path, True
End If
Next
' 進行状況の表示(デバッグ用)
' WScript.Echo "Processing: " & currentFolder.Path
End If
On Error GoTo 0 ' エラーハンドリングを元に戻す
Loop
WScript.Echo "--------------------------------------------------"
WScript.Echo "探索が完了しました。"
' オブジェクトの解放
Set dictQueue = Nothing
Set fso = Nothing
4. ここがプロの分かれ道:3つの重要ポイント
このコードをただコピーするだけでなく、以下の「なぜ」を理解してください。ここがあなたの実力を分けるポイントです。
① `On Error Resume Next` の戦略的運用
実務でフォルダを走査すると、必ず `C:\System Volume Information` のような、管理者であってもアクセスを拒否されるフォルダにぶつかります。
ここでエラーハンドリングを怠ると、プログラムは即座に停止してしまいます。
「エラーが起きることを前提に、エラーが起きたらそのフォルダをスキップして次に進む」。これが堅牢なエンジンの条件です。
② `Scripting.Dictionary` によるメモリ管理
VBScriptの標準的な配列(Array)は、要素が増えるたびに `ReDim Preserve` を使って拡張しなければならず、これは非常にコスト(CPU負荷)が高い処理です。
一方、`Dictionary` オブジェクトは内部的にハッシュテーブルを持っており、要素の追加・削除が極めて高速です。大量のフォルダを扱う場合、この選択がパフォーマンスの差となって現れます。
③ オブジェクトのライフサイクル
`Set currentFolder = fso.GetFolder(currentKey)` のように、ループ内でオブジェクトを生成しています。ループの最後でこれらが適切に処理されないと、メモリリーク(メモリの解放漏れ)の原因になります。
VBScriptはガベージコレクションを行いますが、大規模な処理では、明示的に `Nothing` を代入したり、スコープを意識したりすることが、安定稼働への近道です。
まとめ:次のステップへ
お疲れ様でした!
あなたは今、単なる「マクロの記録」を超えて、「アルゴリズムによる問題解決」というエンジニアの真髄に触れました。
「再帰」は美しく、コードも短くなります。しかし、「非再帰」は強く、実務に耐えうる。
状況に応じて、道具を使い分けられることこそが、真のプロフェッショナルです。
このエンジンをベースに、次は「ファイル名に特定の文字が含まれていたらログに書き出す」といった機能を追加してみてください。それが、あなただけの最強の自動化ツールへと成長していく第一歩です。
ここをクリアすれば、VBScriptの基本はバッチリですよ。自信を持って進んでくださいね!
