和風スパゲティのレシピ

日本語でコーディングするExcelVBA

Dir関数でサブフォルダの一覧を取得する方法

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での取得リスト

こうなってしまいます。

  • 謎のフォルダ「.」「..」が出てくる
  • 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とは雲泥の差ですよね。
Folderオブジェクトの選択肢



FileSystemObjectを使ったコードの一番上にある、

Dim FSO As New FileSystemObject

この対初心者バリアのせいでハードルが高いFSOですが、
こんなのただの呪文ですので無視すればよいのです。


フォルダを操作するマクロを作ることになったら、
FileSystemObjectの勉強を始めてみてください。