迷宮(無限ループ)を突破せよ:FSO再帰探索を「プロフェッショナル」の領域へ引き上げる極意
こんにちは。自動化の荒波を共に渡るエンジニアの皆さん。
「マクロの記録」や、ネットで見つけた断片的なコードを組み合わせて、なんとか動くツールを作れるようになった……。もしあなたが今、そんな段階にいるのなら、おめでとうございます。あなたはすでに「自動化の入り口」を突破しています。
しかし、ここから先には「プロフェッショナル」と「ただ動くコードを書く人」を分かつ、巨大な壁が立ちはだかっています。
その壁の名は、「循環参照(無限ループ)」。
今日は、FileSystemObject(FSO)を使ってフォルダを深く探索する際、多くの初学者が陥り、ベテランですら一瞬油断すると足元を掬われる「NTFSジャンクション/シンボリックリンク」という罠、そしてそれを完璧に制御するための「階層レベル制限」と「訪問済みリスト(Dictionary)」を用いた防御アルゴリズムを伝授します。
ここをクリアすれば、あなたの書くVBScriptは「ただのスクリプト」から「堅牢なシステム」へと進化します。準備はいいですか?
1. なぜ、あなたのプログラムは「止まらなくなる」のか?
FSOを使って「フォルダの中にあるフォルダを、さらにその中にあるフォルダ……」と再帰的に探索するコードを書く際、私たちはつい、以下のような論理で進めてしまいます。
> 「フォルダを見つけたら、その中身をまた同じルールで探しに行く」
これは数学的には美しい再帰構造ですが、Windowsのファイルシステム(NTFS)においては、恐ろしい罠が潜んでいます。それが「ジャンクション(Junction)」や「シンボリックリンク(Symbolic Link)」です。
罠の正体:鏡の中の迷宮
例えば、`C:\Work` というフォルダの中に、`C:\Work\LinkToParent` という名前の「ジャンクション」があるとしましょう。このジャンクションは、中身を覗くと「親である `C:\Work`」を指しています。
もし、あなたのプログラムが何も対策せずにこのリンクを辿ると……
1. `C:\Work` を探索
2. その中の `LinkToParent` を発見
3. `LinkToParent`(中身は `C:\Work`)を探索
4. また `LinkToParent` を発見……
無限ループの完成です。 プログラムはメモリを食いつぶし、CPUを占有し、最終的にはスタックオーバーフロー(メモリの限界突破)でクラッシュします。
2. プロフェッショナルの防具:二重のガード機構
この迷宮を突破するためには、二つの「盾」を装備する必要があります。
① 階層レベル制限(Depth Limit)
「どれだけ深く潜っても、これ以上は行かない」という物理的な限界を決めることです。これにより、万が一ロジックにミスがあっても、プログラムが永遠に暴走し続けることを防ぎます。
② 訪問済みパスリスト(Visited Path List)
「一度通った道は、二度と通らない」という記憶です。`Scripting.Dictionary` オブジェクトを使い、これまでに探索したフォルダのフルパスを記録しておきます。もし次に進もうとした先が、既にリストにあるパスなら、そこは「循環している」と判断して探索を中止します。
3. 実装:堅牢な再帰探索アルゴリズム
それでは、魂を込めたコードを見ていきましょう。このコードはそのまま、実務のスクリプトのベースとして使えるレベルに仕上げています。
' ==========================================================================
' テーマ: NTFSジャンクション/シンボリックリンク対策済み・再帰フォルダ探索
' 設計思想:
' 1. Dictionaryによるパス重複検知 (O(1) の高速検索)
' 2. 探索深度(Depth)による暴走防止
' ==========================================================================
Option Explicit
Dim fso, targetPath, visitedPaths, maxDepth
Set fso = CreateObject("Scripting.FileSystemObject")
Set visitedPaths = CreateObject("Scripting.Dictionary")
' --- 設定 ---
targetPath = "C:\Your\Target\Folder" ' 探索を開始するパス
maxDepth = 10 ' 最大探索深度(これ以上深くは潜らない)
' --- 実行 ---
If fso.FolderExists(targetPath) Then
WScript.Echo "探索を開始します: " & targetPath
' 再帰関数の呼び出し (フォルダオブジェクト, 訪問済み辞書, 現在の深さ, 最大深度)
Call TraverseFolder(fso.GetFolder(targetPath), visitedPaths, 0, maxDepth)
WScript.Echo "探索が正常に終了しました。"
Else
WScript.Echo "エラー: 指定されたパスが存在しません。"
End If
' ==========================================================================
' サブルーチン: TraverseFolder (再帰探索のコア)
' ==========================================================================
Sub TraverseFolder(objFolder, objDict, currentDepth, limitDepth)
Dim subFolder, folderPath
folderPath = objFolder.Path
' --- ガード1: 階層レベル制限 ---
If currentDepth > limitDepth Then
' WScript.Echo "[SKIP] 深度制限到達: " & folderPath
Exit Sub
End If
' --- ガード2: 訪問済みパスのチェック (循環参照対策) ---
' DictionaryのExistsメソッドは非常に高速です。
If objDict.Exists(folderPath) Then
' WScript.Echo "[LOOP DETECTED] 循環参照を検知、スキップします: " & folderPath
Exit Sub
End If
' 現在のパスを「訪問済み」として記録
' Keyにパス、ValueにTrueを格納
objDict.Add folderPath, True
' --- メイン処理: ここにやりたい処理(ファイル一覧取得など)を書く ---
' 例として、フォルダ名を表示
WScript.Echo String(currentDepth * 2, " ") & "|-- " & objFolder.Name
' --- 再帰処理: サブフォルダを探索 ---
For Each subFolder In objFolder.SubFolders
' 再帰呼び出し(深さを +1 する)
Call TraverseFolder(subFolder, objDict, currentDepth + 1, limitDepth)
Next
' 【重要】プロフェッショナルの知恵:
' 探索が終わった後に objDict.Remove(folderPath) をするかどうかは設計次第。
' 1. 完全に「一度通った場所は二度と通らない」なら、Removeしない。
' 2. 「同じ階層の別ルートからの重複」を許容するなら、Removeする。
' 今回の「循環参照対策」としては、Removeしない(=一度見たパスは二度と見ない)のが正解です。
End Sub
4. コードの深淵を読み解く:エンジニアの視点
このコードには、単なる「動くコード」を超えた、いくつかの重要な設計思想が隠されています。
なぜ `Scripting.Dictionary` なのか?
「訪問済みリスト」を作る際、配列(Array)を使いたくなるかもしれません。しかし、配列で「このパスは既にあるか?」を探すには、毎回最初から最後までチェック(線形探索)する必要があり、フォルダ数が増えると劇的に遅くなります。
一方、`Dictionary` オブジェクトはハッシュテーブルを利用しているため、パスがどれほど増えても、一瞬($O(1)$)で存在を確認できます。これが「パフォーマンスの重み」を知る者の選択です。
階層レベル(Depth)の意味
`currentDepth` を引数で渡していく手法は、再帰処理における王道です。`String(currentDepth * 2, ” “)` という部分でインデントを作っていますが、これはデバッグ時に「今、自分がどの階層にいるのか」を視覚的に理解するために極めて重要です。
オブジェクトのライフサイクル
VBScriptにおいて、`FSO` や `Dictionary` などの外部オブジェクトは、使い終わったら解放するのがマナーです。大規模なスクリプトでは、`Set obj = Nothing` を意識することで、メモリリークを防ぎ、システムの安定性を高めることができます。
5. まとめ:一歩先のエンジニアへ
今回の学びを振り返りましょう。
1. NTFSジャンクションは「無限ループ」の入り口である。
2. 「階層制限(Depth Limit)」で、物理的な暴走を止める。
3. 「訪問済みリスト(Dictionary)」で、論理的な循環を止める。
これらを実装できるようになれば、あなたのスクリプトは「何が起きても止まらない、信頼できるツール」へと昇華されます。
プログラミングは、単に命令を並べる作業ではありません。「起こりうる最悪の事態」を想像し、それをエレガントに回避する設計を施すこと。その積み重ねが、あなたを一流のエンジニアへと押し上げます。
さあ、このコードを自分のプロジェクトに組み込んで、その違いを体感してみてください。応援していますよ!
