Dir関数を使って、サブフォルダの一覧を取得する方法を解説します。
はじめに
この方法は非推奨な方法です。
というのも、FileSystemObjectを使った方が圧倒的に簡単だからです。
このため「すべてのサブフォルダを処理」するコードには、
FileSystemObject(以下FSO)を使っているサンプルが多いです。
この処理をきっかけにFSOを知った人も多いかもしれません。
この記事は「なぜDir関数はフォルダ取得が苦手なのかを考察した読み物」だと思って読んでください。
覚えても使うことはあまりありません。
まだFSOを使ったことがなくて、
「FSO勉強するの大変そう。。知ってるDirで何とかならないかな。。」
と思ってここにたどり着いた方も、もしかしたらいらっしゃるかもしれません。
しかし、この記事のこの処理を覚えるよりFSOを覚えた方がはるかに簡単で、
しかも今後につながります。
諦めてFSOの勉強を始めてみてください。
でははじめます。
Dir関数でサブフォルダの一覧を取得する
サンプルではこのフォルダのサブフォルダ「いちご,みかん,りんご」を取得します。
いけそうでダメなコード
まずDir関数でストレートなコードを書いてみます。
Sub サブフォルダの一覧を取得する() Dim 親フォルダパス As String 親フォルダパス = "C:\Users\○○\Desktop\テスト" Dim dirフォルダ名 As String dirフォルダ名 = Dir(親フォルダパス & "\*", vbDirectory) Do While dirフォルダ名 <> "" Debug.Print dirフォルダ名 dirフォルダ名 = Dir() Loop End Sub
ファイルの一覧を取得するコードに「vbDirectory」を足しただけです。
これを実行するとどうなるかというと、
こうなってしまいます。
- 謎のフォルダ「.」「..」が出てくる
- vbDirectoryと言ってるのにファイルも出てくる
という問題が発生していますね。
正しいコード
ひとまず解説はおいておき、正しく動くコードがこちらです。
Sub サブフォルダの一覧を取得する() Dim 親フォルダパス As String 親フォルダパス = "C:\Users\○○\Desktop\テスト" Dim dir結果値 As String dir結果値 = Dir(親フォルダパス & "\*", vbDirectory) Do While dir結果値 <> "" If dir結果値 <> "." And dir結果値 <> ".." Then If GetAttr(親フォルダパス & "\" & dir結果値) And vbDirectory Then Debug.Print dir結果値 End If End If dir結果値 = Dir() Loop End Sub
これで正しく「フォルダ名」だけを出力できます。
「.」「..」が出てきてしまう解説と対策
まず「.」「..」ですが、これは相対パスと呼ばれるもので、以下の意味を持ちます。
- 「.」は自分自身
- 「..」はひとつ上のフォルダ
親フォルダパスは「C:\Users\○○\Desktop\テスト」でしたので、
- 「.」は"C:\Users\○○\Desktop\テスト"
- 「..」は"C:\Users\○○\Desktop"
を表し、例えば以下のコードで1つ上のデスクトップのブックを開くこともできます。
Workbooks.Open 親フォルダパス & "\..\Book1.xlsx"
なんですが、Dir関数でこれが返ってくる意味はないんですよね笑
ぶっちゃけただ邪魔なだけです。
対策は簡単で、ストレートにIfで判定すればOKです。
If dir結果値 <> "." And dir結果値 <> ".." Then
まあ簡単なんですが、ネストが深くなりやすいフォルダ処理において、
IFステートメントがひとつ増えるだけでもかなり邪魔です。
ファイル名も出てきてしまう解説と対策
Dir関数にvbDirectoryを指定してもファイル名まで出てくるのは、
この引数が足し算できるという仕様に起因しています。
Dir関数の第2引数に渡せる値は以下の通りです。
定数 | 値 | 内容 |
---|---|---|
vbNormal | 0 | 標準ファイル |
vbReadOnly | 1 | 読み取り専用ファイル |
vbHidden | 2 | 隠しファイル |
vbSystem | 4 | システムファイル |
vbVolume | 8 | ボリュームラベル |
vbDirectory | 16 | フォルダ |
vbAlias | 32 | エイリアスファイル |
値を見ると2の倍数になっているのがわかりますが、
これは足し算の分解が1パターンになるための値です。
例えば3や7という結果値があったとき、
- 「3」だった場合は「1+2」→読取専用の隠しファイル
- 「7」だった場合は「1+2+4」→読取専用の隠しシステムファイル
という分解しかできないため、1つの数値で複数のパラメータを表すことができます。
この仕組みは2進数にしてみるとわかりやすく、
設定値(10進数) | 2進数 |
---|---|
0 | 00000000 |
1 | 00000001 |
2 | 00000010 |
4 | 00000100 |
8 | 00001000 |
16 | 00010000 |
32 | 00100000 |
64 | 01000000 |
こうなっているため、2進数の各桁を各設定に対応できます。
つまり、
「他の桁がどうなっていようと左側から4桁目が1ならフォルダ」
という風に判定ができるということです。
よって隠しフォルダを探したい場合は、
Dir(フォルダパス & "\*", vbHidden + vbDirectory)
とすればよいわけですが、これは、
Dir(フォルダパス\*, 18)
でも動くということですね。
さてここで問題なのがこいつ↓です。
定数 | 値 | 内容 |
---|---|---|
vbNormal | 0 | 標準ファイル |
0なので常に足していることになってしまうんですね。
' 実際は同じコード Dir(フォルダパス\*, vbHidden + vbDirectory) Dir(フォルダパス\*, vbNormal + vbHidden + vbDirectory)
ということで、悲しいかな標準ファイルは常に検索されてしまい、
これがvbDirectoryでもファイル名まで出てきてしまった原因です。
そしてこの対策にも、引数が足し算という仕様を知っておく必要があります。
対策はこのコードでした。
If GetAttr(パス) And vbDirectory Then
これがなぜ「=」ではなく「And」なのかという話です。
GetAttrはフォルダ・ファイルの属性を調べる関数で、
Dir関数の引数に使ったのと同じ値が返ります。
つまり隠しフォルダだと2+16=18が返ります。
しかし隠しフォルダもフォルダなのですから、
If GetAttr(パス) = vbDirectory Then
これでは不十分ということなんですね。
16=18ではありませんが、18もフォルダには該当しているわけです。
ということで、「足し算の因子に16が入っているもの」をTrueにする必要があり、
16~31、48~63・・・をTrueにしなければいけません。
この計算に「And」を使うことができるのですが詳細は割愛します。
気になる方はビット演算でググってください。
ものすごくざっくりいうと、
ひっ算のルール
- Andは「どっちも1なら1」
- Orは「どっちか1なら1」
実際のAnd計算
- 「00010000」(フォルダ=16)
- 「00010011」(読み取り専用の隠しフォルダ=19)
- ----- And -----
- 「00010000」⇐結果に1個でも1があればTrue
みたいな感じです。
AND比較する2つの値の左から4桁目(vbDirectory担当)がどっちも1ならTrue、
すなわちフォルダであるならTrueと判定するわけですね。
まあ仕組みはこんな感じですが、別に知る必要はないと思います。
「=」じゃダメで「And」でやるんだな~ってだけ覚えておいてください。
FileSystemObjectの場合
さてDirのめんどくささを語ったところで、
FileSystemObjectのコードを見てみましょう。
Sub サブフォルダの一覧を取得する() Dim FSO As New FileSystemObject Dim 親フォルダ As Folder Set 親フォルダ = FSO.GetFolder("C:\Users\○○\Desktop\テスト") Dim 子フォルダ As Folder For Each 子フォルダ In 親フォルダ.SubFolders Debug.Print 子フォルダ.Name Next End Sub
素晴らしくわかりやすいですね。
さらに特筆すべきは、
Debug.Print 子フォルダ.Name
この部分を書き換えて使うときの自由度です。
この部分を
子フォルダ.Path 子フォルダ.Copy 子フォルダ.Move 子フォルダ.Delete
あたりに書き換えるだけで、すべてイメージ通りの動きをしてくれます。
対してDirでやっても手に入るのはパスという文字列だけですからね。
このあとコピーするにも削除するにも移動するにも、
手に入ったパスを使って文字列処理を行う必要があります。
↓この選択肢付オブジェクトが手に入るFSOとは雲泥の差ですよね。
FileSystemObjectを使ったコードの一番上にある、
Dim FSO As New FileSystemObject
この対初心者バリアのせいでハードルが高いFSOですが、
こんなのただの呪文ですので無視すればよいのです。
フォルダを操作するマクロを作ることになったら、
FileSystemObjectの勉強を始めてみてください。