Excel VBA質問箱 IV

当質問箱は、有志のボランティア精神のおかげで成り立っています。
問題が解決したら、必ずお礼をしましょうね。
本サイトの基本方針をまとめました。こちら をご一読ください。

投稿種別の選択が必要です。ご注意ください。
迷惑投稿防止のため、URLの入力を制限しています。ご了承ください。


3301 / 13645 ツリー ←次へ | 前へ→

【62954】ファイルの抽出 さくら 09/9/25(金) 16:57 質問[未読]
【62955】Re:ファイルの抽出 neptune 09/9/25(金) 17:51 発言[未読]
【62970】Re:ファイルの抽出 さくら 09/9/28(月) 13:32 発言[未読]
【62972】Re:ファイルの抽出 kanabun 09/9/28(月) 14:47 発言[未読]
【62979】Re:ファイルの抽出 kanabun 09/9/28(月) 23:42 発言[未読]
【63023】Re:ファイルの抽出 さくら 09/10/1(木) 15:23 発言[未読]
【63026】Re:ファイルの抽出 kanabun 09/10/1(木) 20:24 発言[未読]
【63027】Re:ファイルの抽出 kanabun 09/10/1(木) 20:27 発言[未読]

【62954】ファイルの抽出
質問  さくら  - 09/9/25(金) 16:57 -

引用なし
パスワード
   お力をお貸しください。
「C:\test\」直下に1〜10ののフォルダがあり(例:「1.家計簿」「2.食費」等)
それぞれのフォルダにはexcelファイルやサブフォルダが存在します。
サブフォルダの中にあるものも含め、全てのexcelファイルを抽出し、
フォルダごとにexcel上で一覧表にしたいのです。 (サブフォルダ名は表示させない)

1.家計簿
a.xls
b.xls

2.食費
a.xls
d.xls



現在VBAからBATを実行させてdir関数で全ファイルのパスを取り、
それをexcel上に取り込んで.xlsで終わらないパスを消し、\で分割をかけ…と
スマートとは言いがたい方法のため、VBA上だけで処理する方法ををご教示ください。
どうぞよろしくお願いいたします。

【62955】Re:ファイルの抽出
発言  neptune  - 09/9/25(金) 17:51 -

引用なし
パスワード
   ▼さくら さん:

ここの過去ログを見て研究して下さい。
ht tp://www.vbalab.net/vbaqa/c-board.cgi?cmd=ntr;tree=58198;id=excel

「再帰」で検索を掛ければヒットします。細かく見ればそのものずばり
もあるかもしれません。

【62970】Re:ファイルの抽出
発言  さくら  - 09/9/28(月) 13:32 -

引用なし
パスワード
   ありがとうございます。
お礼が遅くなってしまい大変申し訳有りません。
少々時間が取れず、まだ頂いた情報を確認できていないのですが、取り急ぎお礼を。

後ほど確認、試行後、改めてお礼を書きに参ります。
重複する質問で申し訳有りません&情報ありがとうございました!

【62972】Re:ファイルの抽出
発言  kanabun  - 09/9/28(月) 14:47 -

引用なし
パスワード
   ▼さくら さん:

>重複する質問で申し訳有りません&情報ありがとうございました!

neptune さんがリンクで紹介されているスレッドは主として
検索スピードが話題の中心でしたので、FindFirstFileW 系の
API高速検索を使っていますが、
同じロジックで Dir$関数の再帰処理によるサブフォルダの検索
も可能です。
参考まで。

Sub さくら3()
  Dim LookIn As String: LookIn = "D:\(Data)\" '◆検索Rootフォルダ
  Dim Filename As String: Filename = "*.xls"
  Dim FoundFiles() As String
  Dim nCount As Long
  Dim mCount As Long
  Dim i As Long
  
  'LookIn   : 検索Rootフォルダ
  'Filename  : 検索ファイル名
  'FoundFiles(): マッチしたファイル名の配列()
  'nCount   : ファイルの数
  FileSearch LookIn, Filename, FoundFiles(), nCount, mCount, 0
  
  If (nCount - mCount) > 0 Then
    With Worksheets
      With .Add(After:=.Item(.Count))
        .[A1].Resize(nCount, 2).Value = Trans(FoundFiles())
      End With
    End With
    MsgBox nCount - mCount & "個のファイルがマッチしました"
  End If
End Sub

' サブフォルダを含むファイルの検索
Sub FileSearch(ByVal strDir As String, strFind As String, _
        FoundFiles() As String, nCount As Long, _
        mCount As Long, ByVal Level As Long)

  Dim SubDirs() As String
  Dim strName  As String
  Dim lngCount As Long
  Dim i As Long

  If Right$(strDir, 1) <> "\" Then strDir = strDir & "\"
  strFind = LCase$(strFind)

  If Level <= 1 Then  'Rootフォルダおよび直下のサブフォルダのとき
    nCount = nCount + 1
    mCount = mCount + 1
    ReDim Preserve FoundFiles(1, 1 To nCount)
    FoundFiles(0, nCount) = strDir
  End If

  On Error GoTo err_Dir
  'サブフォルダ名を含む全てのファイルを検索
  strName = Dir$(strDir, vbDirectory Or vbReadOnly _
              Or vbHidden Or vbSystem)
  Level = Level + 1
  While Len(strName)
    If GetAttr(strDir & strName) And vbDirectory Then
      ' サブフォルダならば 配列に一時記憶
      If Not strName Like ".*" Then
         lngCount = lngCount + 1
         ReDim Preserve SubDirs(1 To lngCount)
         SubDirs(lngCount) = strName
      End If

    ElseIf LCase$(strName) Like strFind Then
      ' 検索ファイル名にマッチしたファイル
      nCount = nCount + 1
      ReDim Preserve FoundFiles(1, 1 To nCount)
      FoundFiles(1, nCount) = strName
    End If
Next_Dir:
    strName = Dir$()
  Wend

  ' サブフォルダ内の検索
  For i = 1 To lngCount
    FileSearch strDir & SubDirs(i), strFind, _
         FoundFiles(), nCount, mCount, Level
  Next
  Exit Sub
err_Dir:
  Debug.Print Err().Description, strDir & strName
  Resume Next_Dir
End Sub

'配列のTranspose
Private Function Trans(ff() As String) As String()
  Dim sv() As String
  Dim i As Long, n As Long
  n = UBound(ff, 2)
  ReDim sv(1 To n, 1)
  For i = 1 To n
    sv(i, 0) = ff(0, i)
    sv(i, 1) = ff(1, i)
  Next
  Trans = sv()
End Function

(注)Dir()関数はUnicodeファイル名に対応していないので、
   アラビア語やタイ語のファイル名があると失敗します。
   再帰処理プロシージャ内の
      On Error GoTo err_Dir
   は、そのエラー処理宣言です。

なお、同様の処理はFso(Scripting.FileSystemObject) でも可能ですが、
速度に問題があるので、個人的には Dirを使った再帰Loopのほうが
お手軽かと思います。
もちろん、リンク先の Unicode APIのほうがDir Loopより高速で
パスの長さに対する制限もありませんので、よく使う場合は そちらを
どうぞ(^^

【62979】Re:ファイルの抽出
発言  kanabun  - 09/9/28(月) 23:42 -

引用なし
パスワード
   ↑のコードでは Dirで検索に引っかかった順番でフォルダごとのファイル
リストが出力されるだけなので、シートに一覧を吐き出してから、
サブフォルダごとにファイルを並び替える処理を追加してみました。

Sub さくら3()
  '(宣言部 省略)

  FileSearch LookIn, Filename, FoundFiles(), nCount, mCount, 0
  
  If (nCount - mCount) > 0 Then
    Dim r As Range, c As Range
    With Worksheets
      With .Add(After:=.Item(.Count))
        Set r = .[A1].Resize(nCount, 2)
        r.Value = Trans(FoundFiles())
        Application.ScreenUpdating = 0
        On Error Resume Next
         For Each c In r.Columns(2). _
           Cells.SpecialCells(xlConstants).Areas
           .Sort Key1:=c.Item(1), Header:=xlNo
         Next
        On Error GoTo 0
        Application.ScreenUpdating = 1
      End With
    End With
    MsgBox nCount - mCount & "個のファイルがマッチしました"
  End If
End Sub

【63023】Re:ファイルの抽出
発言  さくら  - 09/10/1(木) 15:23 -

引用なし
パスワード
   ありがとうございます。
せっかくご教授を頂いたにも関わらず、内容をまだ確認できておりません。
確認・検証後、また結果等書き込みに参ります。
急に立て込んでしまい、本当に申し訳有りません。

【63026】Re:ファイルの抽出
発言  kanabun  - 09/10/1(木) 20:24 -

引用なし
パスワード
   ▼さくら さん:

>確認・検証後、また結果等書き込みに参ります。

ちょっと時間があったもので、
上のDir関数をつかったサブフォルダを含むファイル(*.xls)の検索スピードと
FileSystemObjectによる検索と
FindFile APIによる検索スピードを比較してみました。
以下が、あるフォルダを3つの方法で検索したときの処理時間です。

'方法 Hit数   所要時間(ミリ秒)
Dir ( 1113 )  1497 
Fso ( 1122 )  10358
API ( 1122 )   234

ということで、Fsoは論外、Dirはそこそこの時間ですが、ヒットした
ファイルの数が他の方法に比較して少ない(検索漏れがある?)、
やはり APIを使ったほうが 桁違いに時間が短縮されるし、
パスの長さの制限や、Unicodeで検索できるなど安心ですので、
以下に、FindFileW APIを使ったコードを紹介しておきますね

Dirの方法に比べ、コードは
先頭の宣言部にいろいろ宣言があり、とっつきにくいですが、
Sub FindFile() の中でやっていることは
前の Dirによる FileSearch() でやってることと「瓜二つ」
なので、読み比べてみてください。

なお、説明し忘れましたが、
検索フォルダの設定は どちらのマクロも 以下のところです。
> Dim LookIn As String: LookIn = "D:\(Data)\"
ここをそちらの環境に替えてお試しください。

'----------------------------------------------------------
Option Explicit

Private Const MAX_PATH = 260
Private Const INVALID_HANDLE = (-1)

' WIN32_FIND_DATA構造体
Private Type WIN32_FIND_DATA
  dwFileAttributes  As Long
  ftCreationTime   As Currency
  ftLastAccessTime  As Currency
  ftLastWriteTime   As Currency
  nFileSizeHigh    As Long
  nFileSizeLow    As Long
  dwReserved0     As Long
  dwReserved1     As Long
  cFileName(1 To MAX_PATH * 2) As Byte
  cAlternate(1 To 14 * 2) As Byte
End Type

Private Declare Function FindFirstFile _
       Lib "kernel32" Alias "FindFirstFileW" _
        (ByVal lpFileName As Long, _
        lpFindFileData As WIN32_FIND_DATA) As Long
Private Declare Function FindNextFile _
       Lib "kernel32" Alias "FindNextFileW" _
         (ByVal hFindFile As Long, _
         lpFindFileData As WIN32_FIND_DATA) As Long
Private Declare Function FindClose _
       Lib "kernel32" _
         (ByVal hFindFile As Long) As Long
'----------------------------------------------------------
'処理時間計測用
Private Declare Function timeGetTime Lib "winmm.dll" () As Long

'for さくら
Sub さくら3_API()
  Dim LookIn As String: LookIn = "D:\(Data)\"
  Dim Filename As String: Filename = "*.xls"
  Dim FoundFiles() As String
  Dim nCount As Long
  Dim mCount As Long
  Dim t&
  
  t& = timeGetTime()
  'LookIn   : 検索Rootフォルダ
  'Filename  : 検索ファイル名
  'FoundFiles(): マッチしたファイル名の配列()
  'nCount   : ファイルの数
  'mCount   : RootおよびRoot直下のフォルダ数
  FindFile LookIn, LCase$(Filename), FoundFiles(), nCount, mCount, 0
  
  Debug.Print "'API("; nCount - mCount; ")", timeGetTime() - t
  If (nCount - mCount) > 0 Then
    Dim r As Range, c As Range
    With Worksheets
      With .Add(After:=.Item(.Count))
        Set r = .[A1].Resize(nCount, 2)
        r.Value = Trans(FoundFiles())
        Application.ScreenUpdating = 0
        On Error Resume Next
         For Each c In r.Columns(2). _
           Cells.SpecialCells(xlConstants).Areas
           .Sort Key1:=c.Item(1), Header:=xlNo
         Next
        On Error GoTo 0
        Application.ScreenUpdating = 1
      End With
    End With
    MsgBox nCount - mCount & "個のファイルがマッチしました"
  End If
End Sub

Private Sub FindFile(strDir As String, strFind As String, _
        FoundFiles() As String, nCount As Long, _
        mCount As Long, ByVal Level As Long)
 Dim p As String
 Dim fDATA As WIN32_FIND_DATA
 Dim f As String
 Dim hFile As Long
 Dim strName As String
 Dim i As Long
 Dim SubDir() As String, nSub As Long

 If Right$(strDir, 1) <> "\" Then strDir = strDir & "\"
 
 p = strDir & "*.*"  'すべてのファイルを検索
 hFile = FindFirstFile(StrPtr(p), fDATA)
 If hFile = INVALID_HANDLE Then Exit Sub
 
 If Level <= 1 Then
   nCount = nCount + 1
   mCount = mCount + 1
   ReDim Preserve FoundFiles(1, 1 To nCount)
   FoundFiles(0, nCount) = strDir '現在フォルダ名出力
 End If
 Do
   f = fDATA.cFileName
   f = Left$(f, InStr(f, vbNullChar) - 1)
   If fDATA.dwFileAttributes And vbDirectory Then
     If Left$(f, 1) <> "." Then
      ' サブフォルダならば 配列SubDir() に一時記憶
       nSub = nSub + 1
       ReDim Preserve SubDir(1 To nSub)
       SubDir(nSub) = strDir & f
     End If
     
   Else
     strName = LCase$(f)
     If strName Like strFind Then
      ' 検索ファイル名にマッチしたファイルの出力
       nCount = nCount + 1
       ReDim Preserve FoundFiles(1, 1 To nCount)
       FoundFiles(1, nCount) = strName
     End If
   End If
 
 Loop While FindNextFile(hFile, fDATA)
 FindClose hFile
 
 '------------
 'サブフォルダ内の検索
 If nSub Then
   For i = 1 To nSub
     FindFile SubDir(i), strFind, FoundFiles(), nCount, mCount, Level
   Next
 End If
End Sub

Private Function Trans(ff() As String) As String()
  Dim sv() As String
  Dim i As Long, n As Long
  n = UBound(ff, 2)
  ReDim sv(1 To n, 1)
  For i = 1 To n
    sv(i, 0) = ff(0, i)
    sv(i, 1) = ff(1, i)
  Next
  Trans = sv()
End Function

【63027】Re:ファイルの抽出
発言  kanabun  - 09/10/1(木) 20:27 -

引用なし
パスワード
   もうひとつ、説明し忘れました。

↑のコードは 先の Diを使った検索モジュールとは 別の
標準モジュールを追加して、
そこにコピペしてお試しください。

3301 / 13645 ツリー ←次へ | 前へ→
ページ:  ┃  記事番号:
2610219
(SS)C-BOARD v3.8 is Free