フォルダ内の複数のファイルへ同じ処理を行う場合、
ファイルを走査するループ
┗ 各ファイルへの処理
という構成でコードを書きがちですが、これを変えて
まずは対象ファイルのリストを取得
そのリスト内の全ファイルへ処理
このように処理を二段階にして作ることもできます。
こうすることで、
- ファイルの検索とファイルへの処理を別々に実装できる
- 処理が途中で停止したときにも再開しやすい
- リストを手で作って渡すなど応用が利きやすい
- ループが長大にならないためコードが読みやすい
等の様々な利点を得ることができます。
リスト化にはいろいろ種類があり、
例えば「ファイルパス」を文字列として持つ配列などが便利でしょう。
今回はこの「対象ファイルのリスト化」を行う部分を汎用関数化したいと思います。
ファイル一覧取得関数の仕様
今回はFileSystemObjectとCollectionを使用して実装します。
「対象のフォルダパス」と「検索条件」を引数として渡すと、
「対象ファイルのFileオブジェクト」をリスト化して返してくれる関数にしました。
実際の使用例として、
「名前が2025から始まるPDFファイルのうち作成日が10月より前のファイルを削除」
してみましょう。
↓こんな風に使うことができる関数になります。
Sub 名称が2025から始まるPDFファイルのうち作成日が10月以前のファイルを削除する() ' 汎用関数で対象ファイル群をCollectionとして取得 Dim Clct対象ファイル As Collection Set Clct対象ファイル = GetCollectionフォルダ内ファイルリスト( _ "C:\Users\wfsp\Desktop\テストフォルダ", "2025*", "PDF") ' 対象ファイルのうち作成日が10月以前のものを削除 Dim 対象ファイル As File For Each 対象ファイル In Clct対象ファイル If Month(対象ファイル.DateCreated) <= 10 Then 対象ファイル.Delete End If Next End Sub
なかなか便利そうではないでしょうか?
関数の引数には、
- 対象のフォルダパス:C:\Users\wfsp\Desktop\テストフォルダ
- 検索条件:2025*
- 対象拡張子:PDF
を渡しており、これに合致するコレクションを作っているのがすぐわかります。
その後コレクション内をFor Eachで回しているのですが、
ここでFileオブジェクトになっていることが活き、
- DateCreatedプロパティで作成日時を確認可能
- Deleteメソッドでそのファイルを簡単に削除可能
と、情報の取得やファイル操作をそのまま実行することができます。
このようなリスト化する関数は対象をオブジェクトとして取得しておくことで、
その後の処理をかなり書きやすくしてくれる効果があります。
気に入ったら皆さんもお持ち帰りいただき、
自分なりにカスタマイズしてみてください。
またその他の機能として第4引数で「下層フォルダまで探しに行くか」を指定でき、
Trueを渡せばフォルダ内のフォルダも再帰的に検索してくれるようになります。
こちらも必要に応じて使用してください。
なお、本関数はFileSystemObjectを使用しておりますので、
使用する場合はScripting Runtimeの参照設定をONにしてください。
参照設定についてはこちらをどうぞ。
ソースコード
Public FSO As New FileSystemObject ' フォルダ内のファイルリストの取得 Function GetCollectionフォルダ内ファイルリスト(Path対象フォルダ As String _ , Optional ByVal ファイルLike条件 As String = "*", Optional 対象拡張子 As String = "xlsx" _ , Optional is下層フォルダまで取得 As Boolean = False) As Collection ' ファイルが存在しなくても空のコレクションは返す(Nothingは返さない) Set GetCollectionフォルダ内ファイルリスト = New Collection ' 返り値用コレクション Dim Clct対象ファイル As New Collection ' 対象フォルダを取得 If FSO.FolderExists(Path対象フォルダ) = False Then Exit Function Dim 対象フォルダ As Folder Set 対象フォルダ = FSO.GetFolder(Path対象フォルダ) ' 指定フォルダ内のすべてのファイルを走査 Dim ファイル As File For Each ファイル In 対象フォルダ.Files ' 条件を満たすファイルの情報をCollectionに格納して配列へ格納 If ファイル.Name Like ファイルLike条件 & "." & 対象拡張子 Then Clct対象ファイル.Add ファイル End If Next ' 指定フォルダ内のすべてのファイルを走査 ' 下層フォルダも取得する場合の再帰処理 If is下層フォルダまで取得 Then Dim 子フォルダ As Folder For Each 子フォルダ In 対象フォルダ.SubFolders ' 子フォルダへ本関数を再帰実行してDictionaryを取得 Dim Clct子フォルダ対象ファイル As Collection Set Clct子フォルダ対象ファイル = GetCollectionフォルダ内ファイルリスト _ (子フォルダ.Path, ファイルLike条件, 対象拡張子, True) ' 子フォルダのDictionaryを本関数のDictionaryと結合 For Each ファイル In Clct子フォルダ対象ファイル Clct対象ファイル.Add ファイル Next Next ' 指定フォルダ内のすべてのファイルを走査 End If ' 最終結果を返す Set GetCollectionフォルダ内ファイルリスト = Clct対象ファイル End Function
コードの解説
FileSystemObjectを使った教科書的なコードです。
特別な処理は特に行っていません。
設計の話になりますが、今回の返り値をCollectionとしているのは、
For Each ステートメントに「Fileオブジェクト」を渡すためです。
というのもFor Each ステートメントは、
- 配列の中身を回すときはVariant型限定
- Collectionの中身を回すときはObject型もOK
という違いがあり、↓のように書けるのはCollectionだけなのです。
Dim 対象ファイル As File ' ← これを型指定できるのはCollectionだけ For Each 対象ファイル In Clct対象ファイル If Month(対象ファイル.DateCreated) <= 10 Then 対象ファイル.Delete End If Next
これがもし返り値が「一次元配列」になっていると、
Dim 対象ファイル As Variant ' ← ここがかわる For Each 対象ファイル In Clct対象ファイル
こう書く必要が出るということです。
こうなるとFileオブジェクトの選択肢が出てくれなくなるため、
例えば.DateCreatedは手打ちになるし、打ち間違えてもエラーが出なくなります。
これではせっかくFileオブジェクトにした意味がありませんよね。
特定のオブジェクト型をFor Each文で扱おうと思った場合は、
Collection限定となることを覚えておいてください。