[[20200423010332]] 『Dir関数サブファイル含めて検索』(あべ) ページの最後に飛ぶ

[ 初めての方へ | 一覧(最新更新順) | 全文検索 | 過去ログ ]

 

『Dir関数サブファイル含めて検索』(あべ)

Dir関数のサブファイルを含めた検索の書き方に悩んでおります。

ループ処理をしており、
処理する条件として、フォルダ内にブックが存在する場合のみ処理
と設定しているのですが、
ブックはフォルダの中かサブフォルダの中かはそれぞれです。

「If Dir(パス & "\" & fileName & ".xls*") <> "" Then」

 上記1文を書き換えたいと思っています。

"fileName"にはワイルドカードを使用しているため、
Dir関数を使いたいのですが
下記等を参考にしてもうまく作れませんでした。
https://www.moug.net/tech/exvba/0060088.html

ご教授いただけますと幸いです。

< 使用 Excel:Excel2016、使用 OS:Windows10 >


具体的に現状のコードをお示しいただいて、どのような部分で「うまく作れない」のか説明いただくとアドバイスできることがあるかもしれませんが、とりあえず↓も参考になりそうです。
http://officetanaka.net/excel/vba/tips/tips36.htm
https://excel-ubara.com/excelvba5/EXCELVBA233.html

想像ですが、Dir関数"のみ"で再帰処理をしようとしており、2回目?のDir関数で、最初のDir関数がリセットされちゃって困ってるんじゃないでしょうか?

(もこな2 ) 2020/04/23(木) 01:49


 https://www.moug.net/tech/exvba/0060088.html
 に少し手をいれたものです。参考までに。
 A列にファイル名。
 B列にフルパス名を出力します。

 Option Explicit
 Dim cnt As Long
 Sub main()
     Dim rootPath As String

     cnt = 0
     rootPath = "D:\MyDocuments\2020Excel\" ' ■要変更
     Call Sample3(rootPath)
 End Sub

 Sub Sample3(path As String)
     Dim buf As String, f As Object
     buf = Dir(path & "\*.xls*")
     Do While buf <> ""
         cnt = cnt + 1
         Cells(cnt, "A") = buf              'A列にファイル名
         Cells(cnt, "B") = path & "\" & buf 'B列にパス
         buf = Dir()
     Loop
     With CreateObject("Scripting.FileSystemObject")
         For Each f In .GetFolder(path).SubFolders
             Call Sample3(f.path)
         Next f
     End With
 End Sub

(γ) 2020/04/23(木) 02:20


ご返信ありがとうございます。
頂いたものを参考に考えたのですが
エラーの連続でパニック状態です。

突っ込むところは多々かと思いますが、
特に教えていただきたいのは、下記2点です。

1、ファイルが存在する場合のみ実行するとしたいのですが、
 「Call Sample3(rootPath)」の後に何をかけばいいのでしょうか
2、ファイルを探して存在確認をするだけなのすが、
 「Sub Sample3」の下記部分は必要なのでしょうか
   cnt = cnt + 1

      Cells(cnt, "A") = buf              'A列にファイル名
      Cells(cnt, "B") = path & "\" & buf 'B列にパス

ご教授いただけますと幸いです。

Option Explicit

 Dim cnt As Long
 Dim i As Long  '変数が定義されていないとエラーが出たため追加

Sub main()
 For i = 2 To Sheets("対象者").Range("A100000").End(xlUp).Row
 '条件_A行目がaのみ転記
 If (Range("A" & i)) = "a" Then
 
 '既存の条件式_フォルダが存在する場合のみ実行
 'If Dir(ThisWorkbook.path & "\" & fileName) <> "" Then

     '上記1行を書き換えたいです
     Dim rootPath As String
     cnt = 0
     rootPath = ThisWorkbook.path '■要変更"
     Call Sample3(rootPath, "*" & Range("C"& i).Value & "*" & ".xls*")

 '処理1
 '処理2
 '処理3

End IF
End IF
Next i
End Sub

Sub Sample3(path As String)

     Dim buf As String, f As Object

     'buf = Dir(path & "\*.xls*")
     buf = Dir(path & "*" & Range("C"& i).Value & "*" & ".xls*")
     Do While buf <> ""
         cnt = cnt + 1
         Cells(cnt, "A") = buf              'A列にファイル名 '?A
         Cells(cnt, "B") = path & "\" & buf 'B列にパス
         buf = Dir()
     Loop
     With CreateObject("Scripting.FileSystemObject")
        For Each f In .GetFolder(path).SubFolders
            Call Sample3(f.path)
        Next f
    End With
 End Sub

Option Explicit

 Dim cnt As Long
(あべ) 2020/04/23(木) 23:31

私はサブフォルダの中も含めて検索するとの趣旨かと思い、
参考コードを提示しましたが、時期尚早だったようです。

まず、実行したいことを日本語で箇条書きにしてください。
シートに既存の情報があるなら、その説明もしてください。

(γ) 2020/04/23(木) 23:57


申し訳ございません…。

処理したいことは、一覧から個々のシートへ転記作業です。
一覧.xlsmから指定したブックを開いて転記する処理を1000回繰り返します。

 一覧.xlsm 
   ___D___...__S_____
  1  名前     成績
  2  山田      a  ←「山田.xlsx」の「Sheet1」のA5セルに転記
  3  田口      a  ←「田口.xlsx」の「Sheet1」のA5セルに転記
  4  千葉      c  ←「千葉.xlsx」の「Sheet1」のA5セルに転記
  5  羽柴      b  ←「羽柴.xlsx」の「Sheet1」のA5セルに転記
  6  羽生      b  ←「羽生.xlsx」の「Sheet1」のA5セルに転記
  7  上田      a  ←「上田.xlsx」の「Sheet1」のA5セルに転記
 :
 1000

・一覧.xlsmのd列の名前のブックを開く(d列はブック名の一部)
・開いたブックのA5セルに一覧.xlsmのs列の値を転記
・開いたブックを保存して閉じる
・全員分実施
・D列の名前のブックは存在しない場合もある
・サブフォルダに格納されている場合もある

下記マクロは正常に実行できます。
ただ、5行目の下記1行をサブフォルダも含めて
存在確認をするようにしたかった次第です。

'条件_フォルダが存在する場合のみ実行
If Dir(ThisWorkbook.Path & "\" & "*" & Range("C" & i).Value & "*" & ".xls*") <> "" Then

どうぞ宜しくお願い致します。

Sub 練習

  Dim wb1 As Workbook

  '2行目から最終行まで繰り返し実行
   For i = 2 To Sheets("対象者").Range("A100000").End(xlUp).Row

  'G列の値が1のみ実行
   If Range("G" & i).Value = "1"  Then

  '条件_フォルダが存在する場合のみ実行
   If Dir(ThisWorkbook.Path & "\" & "*" & Range("C" & i).Value & "*" & ".xls*") <> "" Then

 '指定のブックを開く(Workbooks.Openにワイルドカード使えないとのことで少し複雑)

   Dim FILENAME As String
   FILENAME = Dir(ThisWorkbook.Path & "\" & "*" & Range("C" & i).Value & "*" & ".xls*")
   Workbooks.Open ThisWorkbook.Path & "\" & FILENAME
   Set wb1 = ActiveWorkbook

   '転記
    wb1.Worksheets("Sheet1").Range("a5") = _
    ThisWorkbook.Worksheets("一覧").Range("S" & i)

    'マクロファイルを保存してブックを閉じる
    wb1.Close SaveChanges:=True

     End If
    End If
   Next i

End Sub
(あべ) 2020/04/24(金) 01:19


 ThisWorkbookの保存されているフォルダの直下、
 およびそのサブフォルダ(それのさらにサブフォルダ等々)を
 対象にファイルを検索したい、というところがポイントの様子。

 この場合、Dir("D:\AAA\*\*.xls*")のようにフォルダ部分にも
 ワイルドカードを使うようなことはできません。

 まず、
 ThisWorkbookの保存されているフォルダ(仮にAフォルダとしましょう)の
 下にあるフォルダはどういう構造になっているのでしょうか。

 A - subB
   - subC
   - subD
 といった程度の構造なんでしょうか。

 それとも、
 A - subB
   - subC  - SubD
           - SubE  - SubF
 などと階層も深く、たくさんのフォルダがあるんでしょうか。

 延べのファイル数、フォルダの数、階層の深さについて
 概略で結構なので教えてください。

 私は、既に示したコードを使って、
 いったんワークシートに、
 ファイル名とフルパス名の一覧を書き出して、
 それを検索したらどうかと思いますが(*)、
 まずは、上記のことについて回答願います。

 (*)勿論色んな方法があるとは思います。
 ・Dirコマンドを標準出力で受けて、配下のフルパス名の配列を作成し、Filterで抽出する方法、
 ・再帰処理のなかで、ファイルのBaseNameをキーに、フルパス名をItemとする辞書を予め作る方法
 などがあるとは思います。一番理解し易いのはどれか考えたいと思っています。

(γ) 2020/04/24(金) 07:54


誠にありがとうございます。

おおよそ下記の通りです。
延べのファイル数:1000
フォルダの数:30
階層の深さ:
():ファイル数
A - sub1(20)

   - sub2(20)
   - sub3(10)
   - sub4(20)
   - sub5(5)
   - sub6(25)
   - sub7(100)
   - sub8(20)
   - sub9(30)
   - sub10(100)
   - sub11(60)
   - sub12(10)
   - sub13 - sub13.1(5)
           - sub13.2(30)
            - sub13.3(25)
            - sub13.5(10)
            - sub13.6(50)
   - sub14 - sub14.1(100)
            - sub14.2(50)
            - sub14.3(10)
  - sub15(50)
  - sub16(80)
  - sub17(5)
  - sub18(10)
  - sub19(100)
  - sub20  - sub20.1(20)
              - sub20.2(100)
              - sub20.3(5)
              - sub20.5(25)
              - sub20.6(10)
  - sub21(80)

以上です。

いったん書き出す、なるほど!そうですね
1つにまとめようと必死でした
(あべ) 2020/04/24(金) 09:01


γさんがコメントを付けているところ横からですが。

どうやら↓から話がずっと続いてるように思います。
[[20200416224751]] 『一覧から別々のファイルへ抽出をしたいです』(あべ)
[[20200421182145]] 『(Dir関数)ループ処理での存在しないファイルのスキップについて』(あべ)

ちょっと思ったのは、「無かったら処理しない」という風に仰ってますが「あったら処理する」って考えたほうがいいんじゃないかな〜なんて思いました。

したがって

 (1) ルートフォルダを指定する

 (2) 再帰処理でファイル名が「山田.xlsx」と一致するものを探す
 (3) (2)で見つかれば、ブックを開いて処理して閉じる

 (4) 再帰処理でファイル名が「田口.xlsx」と一致するものを探す
 (5) (4)で見つかれば、ブックを開いて処理して閉じる

 ・
 ・
 ・
 ・

みたいなアプローチもあるのではないかと思います。

ちなみに、無いブックもあるということですが、逆にフォルダ群の中に同じファイル名のものが複数あったりはしないのでしょうか?
また、処理結果(ファイルが存在したかどうか)もどこかに出力したほうがユーザーに分かりやすいんじゃないでしょうか?

(もこな2 ) 2020/04/24(金) 10:09


もこな2様

そうです!続いております。
以前教えていただいた手順にて作らせていただきました。
その節もありがとうございました!!

「あったら処理する」につきましては、
[[20200421182145]]でもご指摘いただき訂正致しました!
(恐らく出来ているかと…)

フォルダ群の中に同じファイル名のものが複数はございません。
また、処理結果(ファイルが存在したかどうか)は、
処理が完了した際にセルを塗りつぶす指定をしました。

ご教授いただいたアプローチにて処理したいと思っております!
(あべ) 2020/04/24(金) 13:11


 質問への回答ありがとうございました。
 このファイル構造だと、再帰処理で情報を取得するのがよいかと思いました。

 こんな感じではどうですか。
 >再帰処理のなかで、ファイルのBaseNameをキーに、
 >フルパス名をItemとする辞書を予め作る方法
 だけを書きました。申し訳ないが、ここまでとさせてください。

 Option Explicit

 Dim dic As Object
 Dim fso As Object

 Sub main()
     Dim rootPath As String

     Set dic = CreateObject("Scripting.Dictionary")
     Set fso = CreateObject("Scripting.FileSystemObject")

     ''  rootPath = "D:\MyDocuments\2020Excel\" ' テスト用
     rootPath = ThisWorkbook.path        '' 本番

     'key:ファイル名(拡張子除き),Item: フルパス名 からなる辞書(dictionary)を作成
     Call getFiles(rootPath)

     Call 各ブックへの書き込み処理

 End Sub

 Sub 各ブックへの書き込み処理()
     Dim ws          As Worksheet
     Dim wb          As Workbook
     Dim filePath    As String
     Dim personName  As String
     Dim k           As Long

     Set ws = ThisWorkbook.Sheets("一覧") ' "対象者"シートとの差異が不明

     For k = 2 To 3      '最初はいくつかで検証してください。
     ''For k = 2 To ws.Cells(Rows.count, "A").End(xlUp).Row  '本番はこちら
         If ws.Cells(k, "G").Value = 1 Then          '   "1"から変更
             personName = ws.Cells(k, "C").Value
             If dic.Exists(personName) Then          '氏名が登録済みであれば
                 filePath = dic(personName)
                 Set wb = Workbooks.Open(filePath)
                 '転記
                 wb.Worksheets("Sheet1").Range("A5").Value = ws.Cells(k, "S").Value
                 wb.Save
                 wb.Close False
             Else
                 Debug.Print k & "行目:" & personName & "に対するファイル無し"
             End If
         End If
     Next
 End Sub

 Sub getFiles(path As String)
     Dim buf As String, f As Object, s As Object
     Dim ext As String
     Dim baseName As String

     For Each f In fso.GetFolder(path).Files     'パス直下の各ファイルに対して
         ext = fso.GetExtensionName(f.path)      '拡張子
         baseName = fso.GetBasename(f.path)      '拡張子を除いたファイル名
         If LCase(Left(ext, 3)) = "xls" Then     'Excelファイルなら
             If dic.Exists(baseName) Then        '同じ氏名の既登録データがあれば
                 Debug.Print "重複:"; f.path
             Else
                 dic(baseName) = f.path          '辞書に登録
             End If
         End If
     Next

     For Each s In fso.GetFolder(path).SubFolders 'パスの下のサブフォルダに対して
         Call getFiles(s.path)                    '再帰実行
     Next
 End Sub

(γ) 2020/04/24(金) 21:12


こういうアプローチもあるかなぁなんて思います。
    Sub メイン処理()
        Dim i As Long
        Dim ルートフォルダ As String
        Dim MyFolder As Object
        Dim ファイルパス As String

        '▼ダイアログでフォルダを指定する
        With Application.FileDialog(msoFileDialogFolderPicker)
            If .Show = True Then
                Set MyFolder = CreateObject("Scripting.FileSystemObject").GetFolder(.SelectedItems(1))
            Else
                Exit Sub
            End If
        End With

        Stop

        With ActiveSheet
            For i = 2 To .Cells(.Rows.Count, "D").End(xlUp).Row

                '▼「ファイルパス」を初期化してから
                ファイルパス = ""

                '▼「ファイルパス」は"参照渡し"でサブルーチンを呼び出す
                Call サブルーチン(ファイルパス, MyFolder, "*" & .Cells(i, "D").Value & "*.xls?")

                '▼「ファイルパス」に「""」以外が格納されているかで処理分岐
                If ファイルパス <> "" Then
                    MsgBox ファイルパス & vbLf & "↑のブックを開いて処理する"
                Else
                    MsgBox .Cells(i, "D").Value & " を含むファイルは存在せず"
                End If

            Next i
        End With

    End Sub
    '===============================================================================    
    Sub サブルーチン(ByRef ファイルパス As String, ByVal MyFolder As Object, ByVal キーワード As String)
        Dim ファイル As Object
        Dim フォルダ As Object

        Stop

        For Each ファイル In MyFolder.Files
            If ファイル.Name Like キーワード Then
                ファイルパス = ファイル.Path
                Exit For
            End If
        Next ファイル

        Stop

        If ファイルパス <> "" Then Exit Sub

        For Each フォルダ In MyFolder.SubFolders
            Call サブルーチン(ファイルパス, フォルダ, キーワード)
        Next

    End Sub

興味があればステップ実行して研究の上、理解してから、必要な部分のみ、ご自身のコードに取り込んでみてください。(理解していただきたいので、分からないまま丸写しはNGとします)

(もこな2 ) 2020/04/25(土) 11:30


↑の補足。

私の方法だと、毎回アタマから検索することになるので、実行速度は速くないです。
したがって、実行速度が気になるようであれば、γさんのように検索自体を減らすようにしないとダメですね。

(もこな2 ) 2020/04/25(土) 11:57


γさんのコメントにあった、もう一つの方法
 >・Dirコマンドを標準出力で受けて、配下のフルパス名の配列を作成し、Filterで抽出する方法、

 Sub test()
    Dim wb As Workbook
    Dim p As String
    Dim cmd As String
    Dim ss, s
    Dim c As Range

    Application.ScreenUpdating = False

    p = ThisWorkbook.path & "\*.xlsx"
    cmd = "cmd /c dir """ & p & """ /b/s/a-d"
    ss = Split(CreateObject("wscript.shell").exec(cmd).stdout.readall, vbCrLf)

    For Each c In Sheets("一覧").Range("C2:C3000").SpecialCells(xlCellTypeConstants)
        If c.EntireRow.Range("G1").Value = 1 Then
            s = Filter(ss, "\" & c.Value & ".xlsx")
            If UBound(s) >= 0 Then
                Set wb = Workbooks.Open(s(0))
                wb.Sheets("Sheet1").Range("A5").Value = c.EntireRow.Range("S1").Value
                wb.Close True
            End If
        End If
    Next

 End Sub

>フォルダ群の中に同じファイル名のものが複数はございません。

が前提です。
試してませんが、1000ファイルもあると、それなり時間はかかると思います。

(マナ) 2020/04/25(土) 18:27


 もこな2さんのコードを拝見しました。

 今のままですと、
 例えば、質問者さんの示されたフォルダ構造で言えば
 sub1の配下でマッチしたとしても、
 その兄弟フォルダである、sub2以下のファイルたちを、
 (この例ではないですが、親フォルダの兄弟フォルダのファイルたちも)
 延々と調べてしまうので、効率を上げるためになんらかの対応が必要でしょうか。

 (1)
 サブルーチンに入って直ぐのところにも、
 If ファイルパス <> "" Then Exit Sub
 を入れてしまうか、
 (2)
 見つかった段階でErr.Raiseを発生させて、特定ラインにジャンプさせ、
 大域ジャンプの代わりにするんでしょうか。

 癖のようなものでついつい細かいところが気になりますが、
 "富豪的プログラミング"という言葉があるように、
 そういったことはマシン能力でカバーするんだ、というのも整理の仕方として
 あるんでしょうね。余談めいた話で恐縮でした。

 追伸:
 マナさん、フォローありがとうございます。
 この間マナさんに教えていただいたものを知ったかぶりしてしまいましたww。
(γ) 2020/04/25(土) 21:30

>兄弟フォルダも調べてしまう問題
本当ですね・・・

(1)の亜種で、

    Sub サブルーチン(ByRef ファイルパス As String, ByVal MyFolder As Object, ByVal キーワード As String)
        Dim ファイル As Object
        Dim フォルダ As Object

        For Each ファイル In MyFolder.Files
            If ファイル.Name Like キーワード Then
                ファイルパス = ファイル.Path
                Exit For
            End If
        Next ファイル

        If ファイルパス <> "" Then Exit Sub

        For Each フォルダ In MyFolder.SubFolders
            Call サブルーチン(ファイルパス, フォルダ, キーワード)
            If ファイルパス <> "" Then Exit For
        Next
    End Sub

みたいに、呼び出したサブルーチンで見つかっていたらループを抜けるというのはどうですかね。

(もこな2 ) 2020/04/25(土) 21:55


どうもありがとうございました。色々な方法があると思います。

ところで、私の当初の案ですが、

  氏名    それに対するファイルのパス
  山田     D:\AAA\Sub1\山田.xlsx
  ・・・   ・・・・
のような表は、これに限らず、色々な用途で使えるものではないでしょうか。
これを使えば、VLOOKUPでパスも得られますし、
ひとつは持っておくべき表ではないでしょうか。
というより、今までどうやって管理していたんだろう、という気がします。

基本的なところから道具を揃えていくのがよいのではないでしょうか。
私は本当にここで失礼します。

(γ) 2020/04/25(土) 22:40


 >呼び出したサブルーチンで見つかっていたらループを抜けるというのはどうですかね。    
 後出しですみませんが、プロシジャを抜ければ、IF文省略できます。
 が、やっぱり、ファイル数が多いと時間はかかりますね

   Sub sample()
      Dim Path As String, filename As String
      Dim FSO As Object, Fld As Object

      Path = "D:\work"
      filename = "テスト.xls*"

      Set FSO = CreateObject("Scripting.FileSystemObject")

      Set Fld = FSO.GetFolder(Path)

      If FindFile(Fld, filename) Then
         Debug.Print filename
      End If

    End Sub

    Function FindFile(FolderOject As Object, ByRef filename As String) As Boolean
      Dim f As Object
      For Each f In FolderOject.Files
         If f.Name Like filename Then
            filename = f.Path
            FindFile = True
            Exit Function
         End If
      Next
      For Each f In FolderOject.SubFolders
         If FindFile(f, filename) Then
            FindFile = True
            Exit Function
         End If
      Next
    End Function
(´・ω・`) 2020/04/26(日) 07:50

皆さま
コメントありがとうございます。
大変勉強になりました!!
ひとつひとつ実行しており、お礼が遅くなりました。
貴重なお時間誠にありがとうございました!
(あべ) 2020/04/27(月) 22:22

コメント返信:

[ 一覧(最新更新順) ]


YukiWiki 1.6.7 Copyright (C) 2000,2001 by Hiroshi Yuki. Modified by kazu.