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:
- "HOWTO: Search Directories to Find or List Files"
Article ID: Q185476, Visual Basic Knowledgebase
https://support.microsoft.com/support/kb/articles/Q185/4/76.asp