概要:なぜDir関数で「再帰」が必要なのか
Excel VBAで特定のフォルダ内のファイルを取得する際、最も標準的に使われるのが「Dir関数」です。しかし、Dir関数には「指定したフォルダ内のファイルしか列挙できない」という制約があります。実務において、フォルダは階層構造(ツリー構造)になっていることがほとんどです。サブフォルダの中にさらにサブフォルダがあるような深い階層のファイルをすべて取得したい場合、単純なループ処理では対応できません。
ここで登場するのが「再帰呼び出し(Recursive Call)」というアルゴリズムです。自分自身を呼び出す関数を作成することで、階層の深さを気にすることなく、最下層のフォルダまで自動的に探索することが可能になります。本記事では、この高度なテクニックを実務レベルで安全かつ高速に実装するための手法を徹底解説します。
詳細解説:再帰呼び出しのメカニズム
再帰呼び出しとは、ある関数の中で「自分自身を呼び出す」プログラミング手法です。フォルダ探索に応用する場合、以下のようなロジックになります。
1. 現在のフォルダ内のファイルを取得し、処理する。
2. 現在のフォルダ内の「サブフォルダ」を探す。
3. サブフォルダが見つかったら、そのサブフォルダに対して「自分自身(関数)」を呼び出す。
4. これを最下層のフォルダまで繰り返す。
この仕組みの最大の利点は、フォルダが何階層あっても、コード自体はシンプルに保てることです。しかし、注意点もあります。Dir関数はグローバルな状態(検索ハンドル)を保持しているため、再帰処理の中で安易に使うと、親階層の検索状態が破壊されてしまいます。そのため、サブフォルダを取得する際は、一度Dir関数の検索結果を配列やコレクションに退避させるか、FileSystemObject(FSO)を使用するのが定石です。今回は、あえて「Dir関数」の特性を理解した上で、スタックを用いた安全な設計を実装します。
サンプルコード:サブフォルダを含めて全ファイルを列挙する
以下のコードは、指定したパス以下の全ファイルをイミディエイトウィンドウに出力するプロシージャです。
Option Explicit
' メイン処理:検索を開始するプロシージャ
Sub ListFilesRecursively()
Dim targetFolder As String
targetFolder = "C:\TestFolder" ' ここに検索したいパスを入力
If Dir(targetFolder, vbDirectory) = "" Then
MsgBox "指定されたフォルダが存在しません。"
Exit Sub
End If
' 再帰処理の開始
SearchSubFolders targetFolder
MsgBox "ファイル一覧の取得が完了しました。"
End Sub
' 再帰的にフォルダを探索するサブプロシージャ
Sub SearchSubFolders(ByVal folderPath As String)
Dim fileName As String
Dim subFolder As String
Dim folderCollection As Collection
Set folderCollection = New Collection
' 1. 現在のフォルダ内のファイルを列挙
fileName = Dir(folderPath & "\*.*")
Do While fileName <> ""
Debug.Print folderPath & "\" & fileName
fileName = Dir()
Loop
' 2. サブフォルダを取得するために一旦コレクションに退避
' ※Dir関数は再帰の中で呼び出すと状態が壊れるため、先にサブフォルダリストを作る
subFolder = Dir(folderPath & "\*", vbDirectory)
Do While subFolder <> ""
If subFolder <> "." And subFolder <> ".." Then
If (GetAttr(folderPath & "\" & subFolder) And vbDirectory) = vbDirectory Then
folderCollection.Add folderPath & "\" & subFolder
End If
End If
subFolder = Dir()
Loop
' 3. 取得したサブフォルダに対して再帰呼び出しを行う
Dim item As Variant
For Each item In folderCollection
SearchSubFolders CStr(item)
Next item
End Sub
実務アドバイス:安定性とパフォーマンスの向上
上記のコードは学習用として非常にクリアですが、実務で数万ファイルを超えるような大規模な階層を扱う場合は、いくつか考慮すべき点があります。
まず、「FileSystemObject(FSO)の検討」です。
本記事ではDir関数にこだわっていますが、実務ではFSOを使用する方がコードの可読性と安定性が高まります。特に、パスの結合や属性判定においてFSOは非常に強力です。もし、パフォーマンスが極端に重要でないのであれば、FSOの「Folders.Item」コレクションを走査する方がエラーが起きにくく、メンテナンスも容易です。
次に、「エラーハンドリングの実装」です。
ネットワークドライブ上のフォルダや、アクセス権限のないシステムフォルダに触れると、Dir関数やGetAttr関数は実行時エラーを吐きます。必ず「On Error Resume Next」を使用してアクセス失敗をスキップする処理を組み込んでください。
また、「出力先の工夫」も重要です。
Debug.Printは開発時には便利ですが、大量のファイルがある場合、イミディエイトウィンドウが溢れて動作が重くなります。セルに書き出す場合は、最後にまとめて配列から貼り付ける手法(Range.Value = Array)を使うことで、書き込み時間を劇的に短縮できます。
最後に「検索の停止条件」です。
再帰処理は無限ループに陥るリスクがあります(シンボリックリンクなどによる循環参照)。必要に応じて、探索の深さ(Depth)を引数で持たせ、「5階層まで」といった制限を加えることで、意図しない無限ループからアプリケーションを守る設計にしましょう。
まとめ:Dir関数を制する者はVBAを制する
Dir関数を使った再帰処理は、VBAにおける「プログラミング思考」を養うための最高の教材です。単にファイル一覧を取得するだけでなく、階層構造をどうデータとして扱うか、メモリをどう制御するかという視点を持つことで、あなたのVBAスキルは一段上のステージへと引き上げられます。
今回のコードをベースに、特定の拡張子(.xlsxなど)だけを抽出するフィルター機能や、ファイルサイズを条件に加えるなどのカスタマイズを行ってみてください。Dir関数は古くからある関数ですが、そのシンプルさゆえの強力さは、現代の複雑な自動化タスクにおいても決して色褪せることがありません。ぜひ、日々の業務効率化のためにこの技術を活用してください。
