質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

90.40%

  • VBA

    2455questions

    VBAはオブジェクト指向プログラミング言語のひとつで、マクロを作成によりExcelなどのOffice業務を自動化することができます。

【VBA】サブディレクトリも含めたファイル一覧を素早く取得したい

受付中

回答 5

投稿

  • 評価
  • クリップ 2
  • VIEW 7,344
退会済みユーザー

退会済みユーザー

以下のSample1Sample2はどちらもC:\Tempのサブディレクトも含めたファイル一覧を取得する関数です。
Sample1は'Sample2'よりも実行時間が短いですが、コマンドプロンプトが表示されてしまいます。

質問①Sample1でコマンドプロンプトを画面表示させない方法はありますか?
質問②Sample1,Sample2以外に素早くファイル一覧を取得できるような方法はありますか?

Sub Sample1()
    Dim WSH, wExec, sCmd As String, Result As String
    Set WSH = CreateObject("WScript.Shell")
    sCmd = "dir /B /S C:\Temp"
    Set wExec = WSH.Exec("%ComSpec% /c " & sCmd)
    Do While wExec.Status = 0
        DoEvents
    Loop
    Result = wExec.StdOut.ReadAll
    MsgBox Result
    Set wExec = Nothing
    Set WSH = Nothing
End Sub
Sub Sample2()
    Call FileSearch("C:\Temp")
End Sub

Sub FileSearch(Path As String)
    Dim FSO As Object, Folder As Variant, File As Variant
    Set FSO = CreateObject("Scripting.FileSystemObject")
    For Each Folder In FSO.GetFolder(Path).SubFolders
        Call FileSearch(Folder.Path)
    Next Folder
    For Each File In FSO.GetFolder(Path).Files
        Debug.Print File.Path
    Next File
End Sub
  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 5

+1

質問1は分かりません。

質問2は、Sample2は書き方を工夫すれば少し速くできますね。

Option Explicit

Sub Sample2()
    Dim fso As New Scripting.FileSystemObject
    FileSearch fso.GetFolder("C:\")
End Sub

Sub FileSearch(ByVal Folder As Scripting.Folder)
    Dim File As Scripting.File, SubFolder As Scripting.Folder, f As String
    On Error Resume Next
    For Each SubFolder In Folder.SubFolders
        FileSearch SubFolder
    Next
    For Each File In Folder.Files
        f = File.Path
    Next
End Sub

’参照設定で Microsoft Scripting Runtime にチェックを入れてください。

’実行結果(C:\ をスキャン。出力はせず変数に格納するのみ。)
’オリジナル 849秒
’改良後 720秒

’改良箇所
’①参照設定をしてVariantの代わりにその型を使う
’②引数をByValで渡す
’③引数でFolderを渡す


VB.NET を使うともっと速くできます。

Imports System.Collections.Concurrent
Imports System.IO

Module Module1

    Private FileNameBag As New ConcurrentBag(Of String)

    Sub Main()
        Dim sw = Stopwatch.StartNew
        Scan(New DirectoryInfo("C:\"))
        Dim fileNames = FileNameBag.ToArray
        Array.Sort(fileNames)
        Dim scanTime = sw.Elapsed.TotalSeconds
        sw.Restart()
        For Each fileName In fileNames
            Console.WriteLine(fileName)
        Next
        Dim outputTime = sw.Elapsed.TotalSeconds
        Console.WriteLine("ファイル数=" & fileNames.Length & " スキャン時間=" & scanTime & " 出力時間=" & outputTime)
        Console.ReadLine()
    End Sub

    Private Sub Scan(directory As DirectoryInfo)
        Try
            Parallel.ForEach(directory.GetDirectories, Sub(subDir) Scan(subDir))
            For Each file In directory.GetFiles
                FileNameBag.Add(file.FullName)
            Next
        Catch
        End Try
    End Sub

End Module

’実行結果
’ファイル数 358,553個
’スキャン時間 5.98秒
’出力時間 16.37


上記はサンプルですが、Windows アプリにして、コマンドラインパラメータ経由でスキャンするディレクトリ名を入力し、一時ファイルに結果を出力すればコマンドプロンプトを出さずにVBAから容易に利用できると思います。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2017/07/17 05:57

    とてもご丁寧なご回答ありがとうございます。
    助かります!

    キャンセル

0

質問1:
Exec()を使うと、コマンドプロンプトは隠せないようです。
代わりに、Run()を使うように、と先行事例で書いてありました。
stackoverflow

質問2:
ueiさんの回答(細かくてバッチリですね!)を参照ください。

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2017/07/17 07:00

    2006年のマイクロソフトの回答も同じようなことが書いてありました。
    例が分かりにくいのでURLは端折ります。

    キャンセル

  • 2017/07/17 07:10

    Runメソッドは戻り値が整数(終了コードかな?)なので、Dirコマンドで取得したファイル一覧を出力することができないです・・・。
    一時ファイルにDirコマンドの結果を出力して、そのファイルを読み込んで出力するしかないですかね。

    キャンセル

  • 2017/07/17 07:24

    使ったことはないのですが、
    You'd have to redirect the output to a temporary file that your VBScript could then open, read, and delete.
    Yet another possibility is to use the Windows clipboard. You can pipe the output of your command to the clip.exe utility. Then, retrieve the text via any number of available COM objects that can access the contents of the clipboard.
    runを使うと答えも隠れてしまうので、VBSに出力するか、クリップボードにしまうといいよ、とのことでしたので書いた次第です。
    (フォルダリストでもできるかどうかの真偽は不明です。)

    キャンセル

  • 2017/07/17 07:44

    クリップボードの仕組みは全然詳しくないですが・・・
    クリップボードだとマクロ実行している間に、ユーザに他のアプリでクリップボードを使用されてしまうと上書きされてしまうような懸念があります(>_<)

    キャンセル

  • 2017/07/17 08:08

    コマンドプロンプト(クリップボード無し)で完結させようとすると以下の感じでしょうか。
    dir /s /b *.*>list.txt
    # ファイルを書き出してから云々はやっぱり美しくないですね。

    キャンセル

  • 2017/07/17 08:21

    そうですよね。私もそう思います。
    VBSの出力って具体的にどうやるんですかね。

    キャンセル

0

質問②に対する回答ですが、
FSOよりDirを使った方が、数倍早くなります。

後は、検索用に再帰の関数を作って、
サブディレクトリの中も処理していけば、
指定パス以下、全ファイルのパスが取得できるようになります。
※ある程度のプログラミング技術が必要です。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

0

VBAのDir関数を使って、フォルダー配下を再帰的に呼び出してフォルダー階層を取得するサブルーチン(WalkFolderList)を作ってみました。
Dir関数の使い方については、Dir関数でサブフォルダを取得するで調べるのが良いと思います。

作成した関数は、探索するフォルダーの最上位のフォルダーを"C:\Program Files\Windows NT\"のように引数で指定するとその配下のフォルダーをカンマで繫いで表示する。WalkFolderList関数です。

[例]
WalkFolderList("C:\Program Files\Windows NT\で呼び出すとその配下のフォルダーをカンマで繫いで、次のような文字列で返却するものです。
"C:\Program Files\Windows NT\,C:\Program Files\Windows NT\Accessories\,...

コードは以下のようになります。Dir関数を用いて指定されたパス直下のファイル名やフォルダー名からフォルダー名を抜き出し、新しくそのフォルダー名からパスを作り出し自分自身(WalkFolderList)を呼び出すものです。

Public Function WalkFolderList(ByVal s As String) As String
    Dim i As Integer
    Dim v() As String
    Dim folderList As String
    Dim buf As String

    ' 指定されたパスがディレクトリでなかったら何もせずに空文字列を返却する。
    If (GetAttr(s) And vbDirectory) = vbDirectory Then
        WalkFolderList = s
    Else
        WalkFolderList = ""
        Exit Function
    End If

    ' 該当フォルダー直下のフォルダーを動的配列の「v」に格納する。
    buf = Dir(s, vbDirectory)
    i = 0
    Do While buf <> ""
        If buf <> "." And buf <> ".." Then
            If (GetAttr(s & buf) And vbDirectory) = vbDirectory Then
                ReDim Preserve v(i)
                v(i) = s & buf
                i = i + 1
            End If
        End If
        buf = Dir()
    Loop
    If i <> 0 Then
        '該当フォルダー直下のフォルダーの下にフォルダーがあるか自分自身の関数「WalkFolderList」を呼び出す
        For i = LBound(v) To UBound(v)
            WalkFolderList = WalkFolderList & "," & _
                WalkFolderList(v(i) & "\")
        Next i
    End If
End Function

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

0

Sample1 ですが、VBAではなくWindowsの機能を使っているので実行速度は速いですが、マクロとしては
「使わない方がいい」ですね(コードの可読性・セキュリティが低くなる)。

もし出力先を「Debug.Print」にするSample2でしたら、
(前提:ブックのVBEで「ツール」→「参照設定」→「Microsoft Scripting Runtime」にチェックを入れる)

Sub Sample2()
    Call FileSearch("C:\Temp")
End Sub

Sub FileSearch(Path As String)
    Dim FSO As New FileSystemObject, Folders As Folders, Folder As Folder, Files As Files, File As File
    'Set FSO = CreateObject("Scripting.FileSystemObject")→テキストでは
  '「オブジェクト参照回数の増加により、マクロ実行速度低下の原因」となるとのこと。
    Set Folders = FSO.GetFolder(Path).SubFolders
    For Each Folder In Folders
       ' Call FileSearch(Folder.Path)→自身のプロシージャを再帰呼び出ししているため、
    '低下の原因となっている。

      For Each File In Folder.Files
        Debug.Print File.Path
      Next File

    Next Folder

End Sub

こちらで実験しましたが、実行速度及び結果も問題無いと思います。
いかがでしょうか?

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 90.40%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

同じタグがついた質問を見る

  • VBA

    2455questions

    VBAはオブジェクト指向プログラミング言語のひとつで、マクロを作成によりExcelなどのOffice業務を自動化することができます。