以下の内容はhttps://www.limecode.jp/entry/spec/dir-vbdirectory-subfoldersより取得しました。


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の勉強を始めてみてください。




以上の内容はhttps://www.limecode.jp/entry/spec/dir-vbdirectory-subfoldersより取得しました。
このページはhttp://font.textar.tv/のウェブフォントを使用してます

不具合報告/要望等はこちらへお願いします。
モバイルやる夫Viewer Ver0.14