和風スパゲティのレシピ

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

Dirループ中は別関数であっても他のDirは使えない

知らずに落ちると抜け出せなくなるVBAの落とし穴です。

  • Dir関数が、フォルダ内の全ファイルを処理していないのに途中で止まる。
  • 逆にDir関数がいつまでたっても""にならず、無限ループする
  • Dir関数が「プロシージャの呼び出し、または引数が不正です。」エラーを出す。

あたりにお悩みの方は、この落とし穴に落ちていないかご確認ください。

一刻も早く問題を解決したい人へ3行で説明

まず基本として、

' Dir関数でフォルダ内の全ファイルをループする
処理するブック名 = Dir(1の条件)
Do While 処理するブック名 <> ""

    ' ループ内で別の条件の第2Dirを使う2のブック名 = Dir(2の条件)
    
    処理するブック名 = Dir() ' ← こいつが第2Dirを見てしまいループが崩れる
Loop

これはDirの仕様上やってはいけないことですが、

' 同じようにDir関数によるループ中に
処理するブック名 = Dir(1の条件)
Do While 処理するブック名 <> ""

    ' たとえ使われたDirが別の関数であったとしても
    Call 別のプロシージャ
    
    処理するブック名 = Dir() ' ← やっぱりこいつは第2Dirを見てしまう
Loop

Sub 別のプロシージャ()2のブック名 = Dir(2の条件) ' ←犯人
End Sub

こっちもダメだから気をつけないといけません。

というお話です。

解決策は、「どっちかを FileSystemObject で書く」です。
お急ぎでしたら詳しい解説は飛ばして解決策へどうぞ

詳しい解説

Dir関数によるループ処理のサンプル

まずはDir関数で一番メジャーなループ処理である、
「フォルダ内のすべてのブックを処理」をサンプルに書きます。

Sub 指定フォルダ内の売上データを集計する(Path指定フォルダ As String)

    ' くだもの売上データ(○○).xlsx に該当するブックをDirで取得
    Dim 対象ブック名 As String
    対象ブック名 = Dir(Path指定フォルダ * "\くだもの売上データ(*).xlsx")
    
    ' Dirが""を返すまでループ
    Do While 対象ブック名 <> ""
        
        ' ブックを開く
        Dim wb対象ブック As Workbook
        Set wb対象ブック = Workbooks.Open(Path指定フォルダ & "\" & 対象ブック名)
        
        ' ■ 開いたブックを引数に渡して集計プロシージャへ
        Call くだもの売上データを集計シートに追加する(wb対象ブック)

        ' ブックを閉じる
        wb対象ブック.Close False
        
        対象ブック名 = Dir() ' 次のブックへ
    Loop

End Sub

こんな風に■の部分を別のプロシージャにすると、

  • ループに関するコード
  • ループ対象1つ1つの処理に関するコード

が分かれて読みやすくなりますね。

発生するバグ

「売上データを集計シートに追加する」プロシージャの中身に、
こんなコードがあったとします。

' 同じ日付のやさい売上データがあれば、同時に処理する
If Dir(Path指定フォルダ & "\やさい売上データ(" & 売上日 & ").xlsx") <> "" Then
    ~もう一つのファイルを開いてデータをくっつける処理~
End If

一緒に処理をすべきブックがないかを探していますね。


Dir関数を使って。


もう何が言いたいかはお分かりかと思いますが、
ここでDirを使ったせいで、元のループプロシージャの

        対象ブック名 = Dir() ' 次のブックへ
    Loop

この部分が、集計プロシージャの中のDirの続きを探すようになってしまいます。

*のないDirに2個目のファイルはありませんので、ループは止まってしまいますね。


このエラーは、もっとわかりやすく1つの関数で、

対象ファイル名 = Dir(1の条件)
Do While Dirの結果 <> ""2のブック名 = Dir(2の条件)
    
    対象ファイル名 = Dir()
Loop

こんな感じで書かれていると、
明らかにヤバい使い方してると気付けます。

しかし「関数の中で使ってもダメ」っていうのは、
分かっていても見落とす可能性がありますので気をつけましょう。


ちなみに、同じ日のやさい売上データがない場合は
集計プロシージャの時点でDirが""を返しますが、
続けて処理されるループプロシージャのDirでは、
「プロシージャの呼び出し、または引数が不正です。」エラーが出ます。

""を返したDirをもう一度呼ぶと、このエラーが出るのは覚えておきましょう。

このエラーが2回目以降のDirで出たら、

    Do While 対象ブック名 <> ""

こういう""かどうかの判定を書き忘れていないか、
そうでなければまず今回のバグが原因です。

解決策

どちらかを「FileSystemObject」を使った処理に書き換えます。
※ 「FileSystemObject」は説明すると長いので、ここでは解決法のみ書きます。
※ 「FileSystemObject」は以下「FSO」と表記します。

中で使ったDir関数がただのファイル存在チェックの場合

この場合は、存在チェックの部分

' 同じ日付のやさい売上データがあれば、同時に処理する
If Dir(Path指定フォルダ & "\やさい売上データ(" & 売上日 & ").xlsx") <> "" Then
    ~もう一つのファイルを開いてデータをくっつける処理~
End If

これをFSOを使って、こう書き換えることができます。

' 同じ日付のやさい売上データがあれば、同時に処理する
Dim FSO
Set FSO = CreateObject("Scripting.FileSystemObject")
If FSO.FileExists(Path指定フォルダ & "\やさい売上データ(" & 売上日 & ").xlsx") Then
    ~もう一つのファイルを開いてデータをくっつける処理~
End If


が、
わざわざ毎回書き替えるのは面倒だし、忘れてDir関数使ってしまったら元も子もありません。


ということで、「FSO」を使った自作関数を作ってしまって、
フォルダ・ブックの存在チェックは、その関数でやるという手もあります。

その関数がこちら

Function Isフォルダが存在する(Path判定フォルダ As String) As Boolean
    Isフォルダが存在する = CreateObject("Scripting.FileSystemObject").FolderExists(Path判定フォルダ)
End Function

Function Isブックが存在する(判定ブック名 As String, Path対象フォルダ As String) As Boolean
    Isブックが存在する = CreateObject("Scripting.FileSystemObject").FileExists(Path対象フォルダ & "\" & 判定ブック名)
End Function

コピペすればそのまま使えます。

今回の問題の箇所をこの関数で書き替えると、↓こちらになります。

' 同じ日付のやさい売上データがあれば、同時に処理する
If Isブックが存在する(Path指定フォルダ & "\やさい売上データ(" & 売上日 & ").xlsx") Then
    ~もう一つのファイルを開いてデータをくっつける処理~
End If

関数化したことで、

  • 読みやすい。見ただけで内容が分かる。
  • 呼ぶだけで使えるので、FSOのしくみやプロパティ名を覚える必要がない。
  • この関数を呼んでおけば、本記事のバグは起きない。

ようになりましたね。

たった1行を関数にするのは最初は不思議かもしれませんが、
こういう簡単な処理こそ、実は関数化のメリットが大きいです。

関数の作成は躊躇しないようにしましょう。

中で使ったDirでもループなどの処理を行っている場合

この場合は「対処法はこれ」のような特効薬はないです。
FSOでループ部分を書き換えたり、
処理の構造自体を変えたり、
いろいろ考えてやるしかないですね。

もしまだFSOを知らず、Dir関数しか知らない方がこの問題に遭遇している場合は、
これを機にFSOの勉強をおすすめします。

慣れればFSOの方がはるかに読みやすく、オブジェクトを扱う練習にもなりますし、
なによりDirを入れ子にするレベルのマクロを書く必要が生じている時点で、
今後FSOから逃げ切れるとは思えないような気がします。

頑張ってください。