Rekursive Dateisuche

Veröffentlicht: 17. Jul 2000 | Aktualisiert: 16. Jun 2004

Von Mathias Schiffer

Soll eine Datei oder ein Verzeichnis auf einer Festplatte gesucht werden, müssen vom Hauptverzeichnis ausgehend sämtliche Verzeichnisse absteigend bis zur tiefsten Verzeichnisebene nach einer Übereinstimmung mit einem Suchmuster durchsucht werden.

Anwendern von Microsoft Office steht für die Anwendungsentwicklung unter VBA mit dem Objekt "Application.FileSearch" ein komfortables Werkzeug zur Verfügung, das auch unter Visual Basic genutzt werden kann: Binden Sie die Microsoft Office-Typbibliothek als Verweis in Ihr Projekt mit ein, so haben Sie Zugriff auf dieses Objekt. Nachteilig an diesem Vorgehen ist jedoch, dass jeder Anwender Ihrer Software ebenfalls eine entsprechende Version von Microsoft Office installiert haben muss.

Für den Eigenbau eines solchen Objekts (der von der Notwendigkeit einer entsprechenden Office-Version auf dem Kundenrechner befreit) eignet sich ein rekursives Vorgehen, um beim Durchsuchen eines Verzeichnisses jeweils automatisch auch in dessen Unterverzeichnissen nach einer Entsprechung für das Suchmuster fahnden zu können: Wird bei der Suche ein Verzeichnis gefunden, ruft die Routine sich unter Angabe des Verzeichnispfades einfach selbst für das nächste Unterverzeichnis auf.

Für gewöhnlich verwenden Sie für die Ermittlung von Dateien und Verzeichnissen die Visual Basic Funktion "Dir". Für ein rekursives Vorgehen eignet sich diese Funktion jedoch leider nicht ohne weiteres. Ersatzweise können die der "Dir"-Funktion zugrunde liegenden API-Funktionen FindFirstFile, FindNextFile und FindClose direkt verwendet werden:

Declare Function FindFirstFile Lib "kernel32" Alias "FindFirstFileA" ( _ 
  ByVal lpFileName As String, _ 
        lpFindFileData As WIN32_FIND_DATA _ 
  ) As Long 
Declare Function FindNextFile Lib "kernel32" Alias "FindNextFileA" ( _ 
  ByVal hFindFile As Long, _ 
        lpFindFileData As WIN32_FIND_DATA _ 
  ) As Long 
Declare Function FindClose Lib "kernel32" (ByVal hFindFile As Long) As Long

Mit FindFirstFile starten Sie – vergleichbar zum Aufruf der "Dir"-Funktion mit Angabe eines Suchmusters – eine Dateisuche und erhalten als Rückgabewert ein Handle auf diesen Suchvorgang. Auf Basis dieses Handles können Sie für die weitere Suche die Funktion FindNextFile aufrufen, deren Funktion dem parameterlosen Aufruf der "Dir"-Funktion entspricht. Werden keine (weiteren) Dateien gefunden, geben sowohl FindFirstFile als auch FindNextFile den Wert INVALID_HANDLE_VALUE zurück:

Const INVALID_HANDLE_VALUE = -1

In diesem Fall ist die Suche beendet und das erhaltene Handle auf den Suchvorgang muss mittels FindClose wieder freigegeben werden.

Grundlegend für die Verwendung der einzusetzenden API-Funktionen sind die folgenden Typen:

Type FILETIME 
  dwLowDateTime As Long 
  dwHighDateTime As Long 
End Type 
Type WIN32_FIND_DATA 
  dwFileAttributes As Long 
  ftCreationTime As FILETIME 
  ftLastAccessTime As FILETIME 
  ftLastWriteTime As FILETIME 
  nFileSizeHigh As Long 
  nFileSizeLow As Long 
  dwReserved0 As Long 
  dwReserved1 As Long 
  cFileName As String * MAX_PATH 
  cAlternate As String * 14 
End Type

Interessant ist für die Suche nach Dateien neben dem Parameter cFileName (er enthält den Dateinamen des Suchergebnisses) vor allem der Parameter dwFileAttributes (er enthält die Datei-Attribute des Suchergebnisses), der unter anderem Aufschluss darüber gibt, ob das Suchergebnis eine Datei oder ein Verzeichnis beschreibt. Ist das Bit FILE_ATTRIBUTE_DIRECTORY in diesem Parameter gesetzt, handelt es sich beim Suchergebnis um einen Verzeichnisnamen:

Const FILE_ATTRIBUTE_DIRECTORY = &H10

Um alle Unterverzeichnisse eines Verzeichnisses ermitteln zu können, muss für eine rekursive Dateisuche zunächst jedes Verzeichnis nach dem Suchmuster "*.*" durchsucht werden. Die gefundenen Unterverzeichnisse sollten dann in einem Array gespeichert werden, um später die Rekursion über Unterverzeichnisse ausführen zu können. Der folgende Auszug aus dem Sourcecode des Beispielprojekts demonstriert das prinzipielle Vorgehen:

hSearchHandle = FindFirstFile(sPath & "*.*", WinFindData) 
If hSearchHandle <> INVALID_HANDLE_VALUE Then 
  bSearchOn = True 
  Do While bSearchOn 
    ' Handelt es sich um ein Verzeichnis? 
    If (WinFindData.dwFileAttributes And FILE_ATTRIBUTE_DIRECTORY) = FILE_ATTRIBUTE_DIRECTORY Then 
      ' Verzeichnisnamen ermitteln und für den späteren rekursiven 
      ' Aufruf in einem Array speichern 
      sFilename = TrimNull(WinFindData.cFileName) 
      If sFilename <> "." And sFilename <> ".." Then 
        DirectoryCount = DirectoryCount + 1 
        ReDim Preserve Directories(DirectoryCount) 
        Directories(DirectoryCount) = sFilename 
      End If ' sFilename <> "." And sFilename <> ".." 
    End If ' FILE_ATTRIBUTE_DIRECTORY 
    ' Den nächsten Treffer für "*.*" suchen 
    bSearchOn = FindNextFile(hSearchHandle, WinFindData) 
    DoEvents 
  Loop ' While bSearchOn 
  ' Search-Handle schließ;en 
  FindClose hSearchHandle 
End If ' hSearchHandle <> INVALID_HANDLE_VALUE

Nachdem auf diesem Wege die Unterverzeichnisse eines Verzeichnisses in einem Array zwischengespeichert wurden, richtet sich der nächste Schritt nach dem tatsächlichen Suchmuster (etwa "*.txt" für Textdateien). Hierfür wird eine erneute Dateisuche – diesmal mit dem tatsächlich interessierenden Suchmuster – durchgeführt (auch hier soll ein Teil des Beispielsprojekts zur Verdeutlichung angeführt werden):

hSearchHandle = FindFirstFile(sPath & sFileSpec, WinFindData) 
If hSearchHandle <> INVALID_HANDLE_VALUE Then 
  bSearchOn = True 
  ' Solange ein Verzeichnis oder eine Datei gefunden wird: Weitersuchen 
  Do While bSearchOn 
    sFilename = TrimNull(WinFindData.cFileName) 
    If sFilename <> "." And sFilename <> ".." Then 
      If (WinFindData.dwFileAttributes And FILE_ATTRIBUTE_DIRECTORY) = FILE_ATTRIBUTE_DIRECTORY Then 
        ' Gefundener Pfad ist ein Verzeichnis 
        If IncludeDirectoryNames Then 
          mvarFoundFiles.Add sPath & sFilename 
        End If 
      Else 
        ' Gefundener Pfad ist eine Datei 
        RaiseEvent FileFound(sPath, sFilename, False) 
        mvarFoundFiles.Add sPath & sFilename 
      End If ' FILE_ATTRIBUTE_DIRECTORY 
    End If ' sFilename <> "." And sFilename <> ".." 
    ' Nächsten Treffer suchen 
    bSearchOn = FindNextFile(hSearchHandle, WinFindData) 
    DoEvents 
  Loop ' While bSearchOn And Not bStopSearch 
  ' Search-Handle schließ;en 
  FindClose hSearchHandle 
End If ' hSearchHandle <> INVALID_HANDLE_VALUE

Zuletzt verbleibt, für gefundene Unterverzeichnisse eines Verzeichnisses eine Rekursion zu etablieren. Wie Sie dem Beispielprojekt zu diesem Artikel entnehmen können, liegen die obigen Sourcecode-Fragmente in der Prozedur "FindNext". Diese Prozedur wird nunmehr zwecks Rekursion mit allen im ersten Schritt der Suche im Array "Directories" gespeicherten Unterverzeichnissen aus sich selbst heraus aufgerufen:

If DirectoryCount > 0 Then 
  For i = 1 To DirectoryCount 
    FindNext sPath & Directories(i), sFileSpec, True, IncludeDirectoryNames 
  Next i 
End If

Weiterführende Literatur, Beispielprojekt

Zusätzliche Informationen zur rekursiven Dateisuche auf Basis der eingesetzten API-Funktionen finden Sie in der Microsoft Knowledgebase: