フォルダ構成を別のフォルダにコピーするマクロ(1)

全てのサブフォルダのパスを書き出す

再帰呼び出し」を使ったコード

フォルダの中にフォルダがあって、そのフォルダの中にまたフォルダがあって……というような場合に、全てのフォルダのパスを取得するためには、メソッドの「再帰呼び出し」というものを使えば良いらしい。

参考にしたのは、日経ソフトウエア誌2015年10月号の「実務で使うExcelVBA」。VBA界では超有名な武藤玄さんによる連載記事。

日経BPパソコンベストムック」シリーズのいますぐExcelVBAが使えるようになる本にも載っている。今でもフツーに手に入ると思う。

では、コードをば。

リスト1
Sub writeAllFolderPath(ByVal basePath As String)
  Dim objSh As Worksheet
  Set objSh = ActiveSheet
  Dim objSubFolder As Object    '……(1)'
  Dim objRow As Integer
  For Each objSubFolder _
            In CreateObject("Scripting.FileSystemObject") _
              .GetFolder(basePath).SubFolders    '……(2)'
    Call writeAllFolderPath(objSubFolder.Path)    '……(3)'
    objRow = objSh.Cells(Rows.Count, 1).End(xlUp).Offset(1, 0).Row    '……(4)'
    objSh.Range("A" & objRow).Value = objSubFolder.Path
  Next
End Sub

コードの説明

自身の復習も兼ねて、ちょっとこってり説明しておこう。

そもそも、「FileSystemObjectオブジェクト」ってなんだかよく分からなかったんですよ。

名前からしてわけ分からないじゃないですか。ほれ、「ファイルシステムオブジェクトオブジェクト」って、「大瀬ゆめじ・うたじ・うたじ」(ナイツのネタ)みたいで。

でもまあ、オブジェクト指向とかが分かってきはじめた今なら、ちゃんと理解できるんじゃないかと説明を試みることにします。

まずはコイツ。

リスト1の(1)
Dim objSubFolder As Object

「FileSystemObject」オブジェクトってのは、「ファイルシステムに関するアレ」みたいなふうにとらえたらいいのかな。それこそ「フォルダ」とか、「ドライブ」とか、「ファイル」とかいう、データを管理するためのもろもろのアレ。そういう概念を「FileSystemObject」オブジェクトっていうんだと思っている。

当然、Excelにはそんなデータ型は存在しないから、Object型のオブジェクト変数にしとるわけだ。

んで、次。

リスト1の(2)
For Each objSubFolder _
            In CreateObject("Scripting.FileSystemObject") _
              .GetFolder(basePath).SubFolders
Next

1行がやたら長くなるので行継続文字を使っている。「For」~「SubFolders」までが長~い1行。

よく見かけるのは次のような書き方。

Dim FSO As Object
Set FSO = CreateObject("Scripting.FileSystemObject")
With FSO.GetFolder(basePath)
  For Each objSubFolder In .SubFolders
End With

まあ、単にまずFileSystemObjectオブジェクト自体をインスタンス化して変数にセットしてから使っているだけ。FileSystemObjectオブジェクトを後ほど使い回す必要があるんなら、変数にセットした方が使い勝手が良い、というだけだと思う。

とにかく、

CreateObject("Scripting.FileSystemObject").GetFolder(basePath).SubFolders

の部分の処理の順番としては、

  1. FileSystemObjectオブジェクトをインスタンス
  2. GetFolderメソッドに、引数(この場合は「basePath」というフォルダパス)を渡して、そのフォルダパスが指し示すFolderオブジェクト(要するに「フォルダ」そのもの)を取得(参考1参考2
  3. SubFoldersプロパティで「2.」で取得したフォルダ内の全てのサブフォルダをコレクションとして取得(参考

ということだな(間違ってたら教えてくれください)。

これをFor Each ~ Nextで回すわけだから、要するに、

引数basePathが指すフォルダ内のサブフォルダを一つづつ変数objSubFolderにセットして処理を繰り返す

ことになる。

リスト1の(3)

ここが最大のポイント。メソッドが自分自身を呼び出す。これが「再帰呼び出し」ですね。英文法で自分自身を指し示す「myself」のことを「再帰代名詞」と呼ぶのと同じ。斉木しげるとは関係ない。

Call writeAllFolderPath(objSubFolder.Path)

変数objSubFolderには既にFolderオブジェクトが入っているわけだが、そのFolderオブジェクトのPathプロパティ(要するにフォルダのフルパスね)を引数としてwriteAllFolderPathメソッドを呼ぶ。

そうすると、今objSubFolderに入っているフォルダのさらにサブフォルダをコレクションとして取得して……となるわけ。

これ読んで、処理の流れを頭の中だけで理解できる人いるのかな???

というわけで、ちょっと寄り道して処理の流れを見ていこう。

その前にリスト1の(4)の処理だけ見ておく。

リスト1の(4)
objRow = objSh.Cells(Rows.Count, 1).End(xlUp).Offset(1, 0).Row
objSh.Range("A" & objRow).Value = objSubFolder.Path

これは簡単。単に、シートのA列書き込み済み最終行の次の行番号を取得して、そこに変数objSubFolderに格納されているフォルダのフルパスを書き込んでいるだけ。ちょっとくどいかも知れんけど、

CreateObject("Scripting.FileSystemObject").GetFolder(basePath).SubFolders

で取得したサブフォルダのフルパスをその都度シートのA列に書き込んで追加しているだけだ。

再帰呼び出しの挙動

例として、次のような構成のフォルダを作る。

f:id:akashi_keirin:20170326091422j:plain

f:id:akashi_keirin:20170326091429j:plain

f:id:akashi_keirin:20170326091436j:plain

要するに、次のようなフォルダ構成。

f:id:akashi_keirin:20170326091408j:plain

実行

f:id:akashi_keirin:20170326094127j:plain

ループに突入した直後のobjSubFolderの中身は、

f:id:akashi_keirin:20170326094136j:plain

この通り、「フォルダB」。で、「フォルダB」のフルパスを渡してwriteAllFolderPathを呼び出すと、

f:id:akashi_keirin:20170326094127j:plain

f:id:akashi_keirin:20170326094146j:plain

当然objSubFolderの中身は「フォルダC」。んで、さらに「フォルダC」のフルパスを渡してwriteAllFolderPathを呼び出すと、

f:id:akashi_keirin:20170326094212j:plain

今度はもうサブフォルダがないからobjSubFolderはNothingになる。

f:id:akashi_keirin:20170326094222j:plain

この段階でやっとここに処理が移る。

f:id:akashi_keirin:20170326094231j:plain

シートにフォルダのフルパスが書き込まれた。で、次のループ。

f:id:akashi_keirin:20170326094242j:plain

「フォルダB」配下のSubFolderコレクションのうち、「フォルダC」の処理が終わったので、次の「フォルダD」がobjSubFolderに格納されているのが分かる。

実行結果

f:id:akashi_keirin:20170326095913j:plain

全てのフォルダパスが書き出された。

おわりに

なんだか、頭がこんがらがってくるんだけど、ステップ実行しながら挙動を確認したら理解はできると思う。

このメソッドを用いて、フォルダ構成をまるごと別のフォルダにコピーするマクロを完成させていく。

新年度を迎えて、自分の担当業務用のフォルダを丸ごと移したい。だけど、中のファイルはいらない

というときに役に立つと思う。

@akashi_keirin on Twitter