TL;DR —
Dirはただの関数ではありません。最初の呼び出しはパスを渡して最初に一致した ファイル名を受け取り、それ以降の呼び出しは何も渡さず次の名前を受け取ります。""が 返るまで、これが続きます。だからループはこの形になり、その中でDirを二度と呼んでは いけません。
Sub ListWorkbooks()
Dim folder As String, name As String
folder = "C:\Reports\"
name = Dir(folder & "*.xlsx") ' 最初の呼び出し:検索を仕込む
Do While name <> ""
Debug.Print folder & name ' Dir が返すのは名前だけ — フォルダーを前に付ける
name = Dir() ' 引数なし:次の一致
Loop
End Sub
マクロがたくさんのファイルを処理しなければならなくなった瞬間 — 12 の地域別ブックを統合する、
ドロップフォルダーの CSV をすべて取り込む — あなたは Dir に手を伸ばします。参照設定も準備も
いらない組み込みだからです。ところが、この関数はほとんど誰もがつまずく設計を隠しています。
Dir は自分がどこまで進んだかをグローバルに記憶し、二度目の検索がその記憶を無言で消して
しまうのです。この一点さえ理解すれば、「半分のファイルが飛ばされた」たぐいのバグはすべて
消えてなくなります。
この記事で学べること
- 考え方の軸 —
Dirは関数の顔をしたステートフルなイテレーター - 誰もがつまずくたった一つのルール —
Dirループの途中でDirを二度呼ばない Dirが返すのはファイル名だけで、フォルダーは自分で前に付ける必要がある理由- ワイルドカード(
*.xlsx)と、一致がないときのDirの挙動(エラーではなく"") Dirがサブフォルダーへ再帰できない理由 — そして代わりに何を使うか- 名前をまず配列に集めてから、ファイルを開く/移動する/削除するべき理由
考え方の軸:Dir はルックアップではなくイテレーター
たいていの関数は、同じ入力で呼べば毎回同じ答えを返します。Dir は違います。ひとつのフォルダー
検索に対して、単一の、隠れた、モジュール全体で共有されるカーソルを保持するのです。
Dir("C:\Reports\*.xlsx")— 引数が新しい検索を始め、最初の一致を返す。Dir()— 引数なしが同じ検索を進め、次の一致を返す。- もう残っていなければ、
Dir()は空文字列""を返す。
一度だけ仕込んで、あとは前へ進めていくカーソルだと考えてください。上の Do While name <> ""
ループがその定石の形です。パスで仕込み、各回の末尾で引数なしの Dir() を呼ぶ。これがパターンの
すべてであり、丸ごと覚えてしまうべき形です。
誰もがつまずくルール:Dir ループの中で Dir を呼ばない
隠れたカーソルはひとつしかないので、ループの中のどこであれ引数付きの Dir を呼ぶと、
まったく新しい検索が始まり、あなたの現在位置は捨てられます。これがナンバーワンの Dir バグで、
しかも完全に理にかなって見えるだけに、たちが悪いのです。
name = Dir(folder & "*.xlsx")
Do While name <> ""
If Dir(folder & "flag.txt") <> "" Then ' ← 大惨事:カーソルをリセットする
Process folder & name
End If
name = Dir() ' これは *.xlsx ではなく "flag.txt" の検索を続けてしまう
Loop
内側の Dir(folder & "flag.txt") は、別のパターンでエンジンを再起動します。その後ループが
Dir() を呼ぶと、進むのはそちらの検索です — だから間違ったファイルを処理したり、.xlsx の
大半を飛ばしたり、いつまでも回り続けたりします。ルールは絶対です。Dir ループの中で呼んで
よい Dir は、引数なしの Dir() だけ。ループの途中で別のファイルの有無を調べたいなら、代わりに
FileSystemObject.FileExists を使いましょう — こちらには
壊すべき共有カーソルがありません。
Dir が返すのは名前であって、パスではない
Dir が返すのは裸のファイル名 — C:\Reports\March.xlsx ではなく March.xlsx です。これを
忘れるのが 2 番目に多い失敗で、というのも次にやることはたいていファイルを開くことだからです。
Set wb = Workbooks.Open(name) ' エラー 1004 — "March.xlsx" はフルパスではない
Set wb = Workbooks.Open(folder & name) ' 正しい — フルパスを組み立て直す
フォルダー文字列はつねに別に持っておき、フルパスが必要になったら連結し直しましょう。フォルダー
変数の末尾がバックスラッシュ("C:\Reports\")で終わっているのもこのためです — こうすれば
folder & name は区切りの欠けない正しいパスになります。
ワイルドカードと「一致なし」のケース
最初の Dir に渡す引数は、パスとパターンです。* は任意の長さの文字列に、? は 1 文字に
一致します。
Dir("C:\Reports\*.xlsx")— フォルダー内のすべての.xlsxDir("C:\Reports\Q?-2026.xlsx")—Q1-2026.xlsx、Q2-2026.xlsx、…Dir("C:\Reports\March.xlsx")— ワイルドカードなし。だから 1 つのファイルを試すだけで、 存在すれば"March.xlsx"を、なければ""を返す(これが ファイルの存在確認の 1 行版の正体です)
一致がなければ、Dir は "" を返します — エラーは発生しません。Do While name <> "" が
「そもそもファイルが 1 つもない」と「末尾に達した」を同じテストでさばけるのは、このためです。
ひとつの罠:既定では Dir はフォルダーにも一致し、隠しファイルやシステムファイルは飛ばします。
たまたまパターンに一致するサブフォルダーがあれば、その名前が返ってくることもあります。それが
問題になる場面では、vbNormal 属性を明示的に渡すか、FileSystemObject
で確かめましょう。
Dir は再帰できない — そしてループ中にフォルダーを変更すると壊れる
フラットな一覧より先へ進もうとすると、2 つの厳しい制約が Dir から離れることを迫ります。
再帰的ではない。 Dir はひとつのフォルダーを歩きます。サブフォルダーへ降りていく引数は
なく、Dir ループを 2 つ入れ子にすることもできません(カーソルはひとつ、思い出してください)。
C:\Reports\2026\Q1\...、Q2\... のようなツリーを処理するには、SubFolders コレクションが
再帰のために作られている FileSystemObject が必要です。
ループしながらフォルダーを変更すると、列挙が壊れる。 Dir ループの最中にファイルを Kill、
リネーム、移動、あるいは Open して SaveAs すると、隠れたカーソルが歩いているまさにその
フォルダーを書き換えていることになります。名前が飛ばされたり、重複したりします。直し方は
2 パスのパターン — まず集め、あとで動くです。
Dim names() As String, n As Long
ReDim names(1 To 1000)
Dim f As String: f = Dir(folder & "*.xlsx")
Do While f <> "" ' パス 1:名前を集めるだけ
n = n + 1: names(n) = f
f = Dir()
Loop
Dim i As Long
For i = 1 To n ' パス 2:ここで開く/移動/削除しても安全
Process folder & names(i)
Next i
名前がいったん配列に入ってしまえば、壊れやすいカーソルの出番は終わり、ファイルに対して好きな ことができます。
正直な結論:Dir は本当は何のためか
Dir はたったひとつの仕事に対する正しい道具です — 単一フォルダー内のファイルを、素早く、
フラットにループすること。追加する参照も、生成するオブジェクトもありません。「このフォルダーの
全 .xlsx に対してこのマクロを走らせる」なら、ほかのどんな方法より短く速い。用途をそこにとどめ、
4 つのルールを守りましょう。
- 一度仕込んで、
Dir()で進める — ループの中の唯一のDirは引数を取らない。 - フォルダーを前に付ける —
Dirは名前を返す。folder & nameでパスを組み立て直す。 - 変更するなら 2 パス — 名前を配列に集め、それから開く/移動する/削除する。
- サブフォルダー、ファイルのサイズや日付、あるいはループ途中での 2 つ目の存在確認が必要に
なった瞬間、
FileSystemObjectに手を伸ばす。
同じループの中で 2 つ目の Dir が欲しいと感じたその瞬間こそ、Dir を使いこなす段階を超えた
という合図です — 直すべきバグではなく、切り替えるべき道具なのです。
ExcelMaster の活用
ファイルループを正しく書くとは、Dir を一度だけ仕込み、引数なしの呼び出しで進め、フルパスを
組み立て直し、フォルダーの変更が 2 パスの配列を強いるタイミングを知っている、ということです —
「ただフォルダーのファイルをループするだけ」にしては驚くほどの手続きの多さで、たった 1 回の
はぐれた Dir 呼び出しが、その全体を静かに壊してしまいます。
ExcelMaster は、ループを代わりに
書きます。ジョブを説明すれば — 「このフォルダーの全ブックを開いて、合計を取り出して、それぞれ
閉じて」 — 正しい Dir ループ(サブフォルダーが必要なら FileSystemObject のウォーク)を、
フォルダーの連結つき、ファイルを移動するときの 2 パス配列つき、そして反復ごとに対応する
wb.Close つきで生成します。あなたは結果を説明するだけ。
イテレーターの記憶はそれが面倒を見るので、二度と噛みつかれません。
よくある質問
VBA でフォルダー内の全ファイルをループするには?
Dir にパスとワイルドカードを渡して仕込み、引数なしの Dir() で進めます:
name = Dir("C:\Reports\*.xlsx")、それから Do While name <> "" … name = Dir() … Loop。
Dir は各ファイル名を順に返し、フォルダーを使い切ると "" を返します。Dir が返すのは名前
だけなので、フルパスが必要なときは folder & name を使いましょう。
VBA の Dir ループがファイルを飛ばしたり永遠に回ったりするのはなぜですか?
ほぼ必ず、ループの中で引数付きの Dir を呼んだからです。Dir は単一の隠れたカーソルを
保持しており、Dir(somePath) はどれも新しい検索を始めて現在位置を消してしまいます — すると
ループ末尾の Dir() は間違った検索を進めます。Dir ループの中では、つねに引数なしの Dir()
だけを呼びましょう。ループの途中で別のファイルを調べるには、共有状態を持たない
FileSystemObject.FileExists を使います。
VBA の Dir はサブフォルダーを検索しますか?
いいえ。Dir は単一のフォルダーを歩き、再帰の仕組みを持ちません。カーソルはひとつしかないので、
Dir ループを 2 つ入れ子にすることもできません。フォルダーツリーを歩くには、
FileSystemObject を使い、その SubFolders コレクションを再帰的に
反復しましょう。
VBA の Dir からフルパスを得るには?
Dir が返すのは裸のファイル名(March.xlsx)で、フルパスではありません。フォルダーを
それ自身の変数に(末尾をバックスラッシュにして)保持し、連結します:folder & name で
C:\Reports\March.xlsx になります。裸の名前を Workbooks.Open に渡すと、実行時エラー 1004 に
なります。
ファイルをループするのに Dir と FileSystemObject のどちらを使うべきですか?
準備なしで単一フォルダーを素早くフラットにループするなら Dir。サブフォルダーへ再帰したい、
各ファイルのサイズや更新日を読みたい、ループしながら別のファイルを確認したい — Dir の単一の
隠れたカーソルが邪魔になるすべての場面では、FileSystemObject に
切り替えましょう。
検証環境
検証環境: Excel 365 (Windows 11), VBA 7.1 — 最終確認 2026-08-21。
関連ガイド: VBA FileSystemObject · VBA Check If File Exists · VBA Open Workbook · VBA Close Workbook · VBA For Loop
