和風スパゲティのレシピ

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

深い階層のフォルダ作成を再帰関数で行う

MkDirステートメントやFileSystemObjectのCreateFolderメソッドは、
作りたいフォルダの親フォルダがないとエラーになります。


例えば以下のコードは「商品フォルダしかない」状況では実行できません。

FSO.CreateFolder "C:\Users\○○\Desktop\商品\くだもの\柑橘\みかん"


この目的を果たすためには、

FSO.CreateFolder "C:\Users\○○\Desktop\商品\くだもの"
FSO.CreateFolder "C:\Users\○○\Desktop\商品\くだもの\柑橘"
FSO.CreateFolder "C:\Users\○○\Desktop\商品\くだもの\柑橘\みかん"

このように上から順次実行されるようコードを書く必要があります。


この解決策としてストレートで分かりやすいのは、

  1. フォルダパスを\でSplitして各階層ごとのフォルダ名に分ける
  2. 各フォルダ名を1つずつつなげて上から順にフォルダを作る

というコードかなと思います。

中核のコードだけ抜粋すると、

Arr階層ごとのフォルダ名 = Split(作成フォルダのフルパス, "\")

For i = 1 To UBound(Arr階層ごとのフォルダ名)
    作成フォルダパス = 作成フォルダパス & "\" & Arr階層ごとのフォルダ名(i)
    MkDir 作成フォルダパス
Next

こんなコードです。


詳細についてはこちらをどうぞ。

www.limecode.jp



さてこの処理を行うもう一つの方法として、
再帰関数を用いる方法があります。


それがこちら

Sub フォルダを作成する(作成フォルダパス As String)
    
    Dim 親フォルダパス As String
    親フォルダパス = FSO.GetParentFolderName(作成フォルダパス)
    
    If FSO.FolderExists(親フォルダパス) = False Then
        Call フォルダを作成する(親フォルダパス)
    End If
    
    FSO.CreateFolder 作成フォルダパス

End Sub

短くていいですね。


再帰関数にあまり触れたことがない方向けに説明しますと、
再帰関数とは「自分自身を呼ぶ関数」のことです。

上記の関数も自分で自分をCallしている部分がありますね。


再帰処理に慣れていないと処理の順番を追いづらいと思いますので、
実際に処理される様子をコードに展開してみます。


例えば以下のコードを実行してみましょう。

Call フォルダを作成する("C:\Users\○○\Desktop\商品\くだもの\柑橘\みかん")


「商品」フォルダが存在し、「くだもの」フォルダが存在しない場合は、
以下のようにコードが実行されることになります。

Sub フォルダを作成する("C:\Users\○○\Desktop\商品\くだもの\柑橘\みかん")
    親フォルダパス = "C:\Users\○○\Desktop\商品\くだもの\柑橘"
    
    If FSO.FolderExists(親フォルダパス) = False Then ' ← True
        
        Sub フォルダを作成する("C:\Users\○○\Desktop\商品\くだもの\柑橘")
            親フォルダパス = "C:\Users\○○\Desktop\商品\くだもの"

            If FSO.FolderExists(親フォルダパス) = False Then ' ← True
                
                Sub フォルダを作成する("C:\Users\○○\Desktop\商品\くだもの")
                    親フォルダパス = "C:\Users\○○\Desktop\商品"
                    If FSO.FolderExists(親フォルダパス) = False Then ' ←ここでFalse

                    FSO.CreateFolder "C:\Users\○○\Desktop\商品\くだもの"
                End Sub
                
            End If
            FSO.CreateFolder "C:\Users\○○\Desktop\商品\くだもの\柑橘"
        End Sub
        
    End If
    FSO.CreateFolder "C:\Users\○○\Desktop\商品\くだもの\柑橘\みかん"
End Sub
  1. 親フォルダがない間は自分自身をCallし続けて深くなっていき、
  2. 親フォルダが見つかると一転、そこからフォルダを作成して戻ってくる。

というイメージが伝わりますでしょうか?


最後のフォルダ作成部分が、しっかりと

FSO.CreateFolder "C:\Users\○○\Desktop\商品\くだもの"
FSO.CreateFolder "C:\Users\○○\Desktop\商品\くだもの\柑橘"
FSO.CreateFolder "C:\Users\○○\Desktop\商品\くだもの\柑橘\みかん"

この形で実行されているのを見ると、よく出来てるな~って思いますよね。


こんな感じで「自分自身を呼ぶ関数」が再帰関数です。


今回の例のように、「どこまで処理を進めるか」が不定の時に、
その終点が見つかるまで自身を呼び続けていくような使い方が多いです。

探索系のプログラムに使われることが多いロジックでしょうか。


VBAではめったに見ることはありませんし、
再帰関数を知っていると使える場面に遭遇するかというと、
そうでもなさそうな気はします。

子フォルダも含めたフォルダ内のすべてのファイルを処理する
ときにたまに出てくるくらいでしょうかね。


ロジックとしてはとても面白いと思いますので、
興味があれば、他の再帰関数も調べてみてください。

おまけ:再帰関数の関数名

個人的にずっと悩んでいることなのですが、
再帰関数ってどういう風に名付ければいいんでしょうかね。


例えば今回の関数

Sub フォルダを作成する(作成フォルダパス As String)
    
    Dim 親フォルダパス As String
    親フォルダパス = FSO.GetParentFolderName(作成フォルダパス)
    
    If FSO.FolderExists(親フォルダパス) = False Then
        Call フォルダを作成する(親フォルダパス)
    End If
    
    FSO.CreateFolder 作成フォルダパス

End Sub

これって、再帰関数であることは関数名からはわからないじゃないですか。


よく「コメントなしでもロジックがわかるように」なんていわれますけど、
再帰処理ってどういう風に関数名に表現すればいいのかなって。


まあ、

Sub フォルダを作成する(作成フォルダパス As String)
    
    Dim 親フォルダパス As String
    親フォルダパス = FSO.GetParentFolderName(作成フォルダパス)
    
    ' 親フォルダが存在する階層まで遡って再帰呼び出し
    If FSO.FolderExists(親フォルダパス) = False Then
        Call フォルダを作成する(親フォルダパス)
    End If
    
    FSO.CreateFolder 作成フォルダパス

End Sub

このコメントで十分わかるんですけどね。

呼出元が「この関数は再帰関数だ」と認識する必要もないですし。



もし再帰ロジックを関数名で表現するなら、
ユーザーが呼ぶ関数と実際の再帰部分は別にして、

Sub フォルダを作成する(作成フォルダパス As String)
    
    If Dir(作成フォルダパス, vbDirectory) <> "" Then Exit Sub
    
    Call 親フォルダがなければ再帰呼出で親フォルダを作成してからフォルダを作成する(作成フォルダパス)
    
End Sub

Private Sub 親フォルダがなければ再帰呼出で親フォルダを作成してからフォルダを作成する(作成フォルダパス As String)
    
    Dim 親フォルダパス As String
    親フォルダパス = FSO.GetParentFolderName(作成フォルダパス)
    
    If FSO.FolderExists(親フォルダパス) = False Then
        Call 親フォルダがなければ再帰呼出で親フォルダを作成してからフォルダを作成する(親フォルダパス)
    End If
    
    FSO.CreateFolder 作成フォルダパス

End Sub

こんな感じになるんですかね。

わかりやすいけど長いですよね。


こんな感じで関数名をとにかく丁寧に名付け隊の私は、
再帰関数が出てくるたびにいっつも命名に悩みます。


出てくるたびにといっても、実務ではめったに扱うことはなく、
Twitterのプログラミングお題の回答とかがほとんどなため、
そんな関数名なんてFnでいいはずなんですけどね。

それでもやっぱり気になるものは気になるんですよ。


ということでみなさんも、再帰関数を使う際は関数名に悩んでみてください。