Dateimasken bzw. Wildcards in VBScript
VBScript bietet keine Möglichkeit, z.B. beim Einlesen einer Files Collection eine Dateimaske vorzugeben; es werden immer alle Dateien des betreffenden Verzeichnisses geliefert. Aus gegebenem Anlass benötigte ich aber dieses Feature und habe deshalb eine VBS-Klasse für diesen Zweck geschrieben, deren Code ich hiermit der Öffentlichkeit zur Verfügung stellen möchte.
Der Code liefert nicht die selben Ergebnisse wie der
Das Fragezeichen steht für ein beliebiges Zeichen, das genau einmal vorkommen muss. Bei folgender Verzeichnisstruktur
liefert der Befehl
wie erwartet kein Ergebnis. Der Befehl
liefert jedoch beide Dateinamen zurück, was nicht der Definition von
Jetzt aber erstmal der Code:
Die öffentliche Funktion
Die Hauptschleife in den Zeilen 18 bis 39 prüft die einzelnen Zeichen des Dateinamens und der Suchmaske auf Gleichheit. Ein Fragezeichen in der Suchmaske wird als Übereinstimmung gewertet.
Wenn ein Stern/Asterisk in der Suchmaske gefunden wird, wird zunächst geprüft, ob es sich um das letzte Zeichen der Suchmaske handelt. Wenn ja haben wir einen Treffer. Wenn nicht, ruft sich die Funktion in Zeile 33 selbst auf. Die Position des Zeichenzeigers in die Suchmaske wird für die neue Instanz um eins erhöht (
Wenn die neue Instanz keine Übereinstimmung gefunden hat, wird nach der Rückkehr der Positionszeiger
Zeile 41 dient dazu den Fall zu erkennen, dass die Hauptschleife beendet wurde, weil nur einer der beiden Strings bis zum letzten Zeichen verarbeitet wurde. Dann liegt kein Treffer vor.
Zeile 42 ist für den Sonderfall, dass der Dateiname schon vollständig verarbeitet wurde, der Positionszeiger für die Suchmaske aber erst auf dem letzten Zeichen steht und dieses Zeichen ein Asterisk ist. Dann liegt ein Treffer vor (Ausnahme von der Ausnahme ).
Jetzt ein Beispiel zur Verwendung. Der obige Code muss in die Datei mit dem folgenden Code kopiert werden.
Aufruf:
In Zeile 19 wird eine Files Collection erzeugt, die alle Dateien des übergebenen Verzeichnisses enthält. Jeder Dateiname wird in Zeile 22 auf Übereinstimmung mit dem übergebenen Muster geprüft und bei einem Treffer das File Objekt in das Array
Über die Abfrage in Zeile 28 lässt sich feststellen, ob passende Dateien gefunden wurden. Es wird dann durch das Array iteriert, um mit jeder Datei die vorgesehene Aktion auszuführen. Im Beispiel wird der komplette Dateipfad angezeigt und die Datei zum Lesen geöffnet.
Gruß
Friemler
Der Code liefert nicht die selben Ergebnisse wie der
DIR
-Befehl der Kommandozeile, denn dessen Pattern Matching Algorithmus ist, wie so viele Konsolenbefehle, nicht fehlerfrei implementiert, d.h. er hält sich nicht exakt an die definierte Bedeutung des Wildcardzeichens ?
.Das Fragezeichen steht für ein beliebiges Zeichen, das genau einmal vorkommen muss. Bei folgender Verzeichnisstruktur
test.text
test2.txt
dir /b t?t*.txt
dir /b t??t????????.txt
?
entspricht. Der hier vorgestellte Code liefert das korrekte Ergebnis, nämlich nichts.Trotzdem möchte ich betonen, dass der Code zwar getestet ist, aber trotzdem die Möglichkeit besteht,
dass er in speziellen Fällen falsche bzw. unvollständige Ergebnisse liefert. Verwendung auf eigene Gefahr!
Hinweise diesbezüglich sind mir sehr willkommen.
dass er in speziellen Fällen falsche bzw. unvollständige Ergebnisse liefert. Verwendung auf eigene Gefahr!
Hinweise diesbezüglich sind mir sehr willkommen.
Jetzt aber erstmal der Code:
Class clsFileNameMatch
Private objFSO
Private Sub Class_Initialize()
Set objFSO = CreateObject("Scripting.FileSystemObject")
End Sub
Private Sub Class_Terminate()
Set objFSO = Nothing
End Sub
Private Function FindMatch(ByRef strString, ByRef strPattern, ByVal intIdxStr, ByVal intIdxPattern)
Dim chrString, chrPattern
While (intIdxStr <= Len(strString)) And (intIdxPattern <= Len(strPattern))
chrString = Mid(strString, intIdxStr, 1)
chrPattern = Mid(strPattern, intIdxPattern, 1)
If chrPattern <> "*" Then
FindMatch = (UCase(chrString) = UCase(chrPattern) Or chrPattern = "?")
If Not FindMatch Then Exit Function
intIdxStr = intIdxStr + 1
intIdxPattern = intIdxPattern + 1
Else
FindMatch = (intIdxPattern = Len(strPattern))
If FindMatch Then Exit Function
Do
FindMatch = FindMatch(strString, strPattern, intIdxStr, intIdxPattern + 1)
intIdxStr = intIdxStr + 1
Loop Until FindMatch Or intIdxStr > Len(strString)
Exit Function
End If
Wend
If (intIdxStr <= Len(strString)) Or (intIdxPattern <= Len(strPattern)) Then FindMatch = False
If (intIdxStr > Len(strString)) And (intIdxPattern = Len(strPattern)) And (Right(strPattern, 1) = "*") Then FindMatch = True
End Function
Public Function FileNameMatch(ByRef strFileName, ByRef strPattern)
Dim strName, strExt, strPatternName, strPatternExt
strName = objFSO.GetBaseName(strFileName)
strExt = objFSO.GetExtensionName(strFileName)
strPatternName = objFSO.GetBaseName(strPattern)
strPatternExt = objFSO.GetExtensionName(strPattern)
If strPatternName = "" Then strPatternName = "*"
If strPatternExt = "" Then strPatternExt = "*"
FileNameMatch = FindMatch(strName, strPatternName, 1, 1) And FindMatch(strExt, strPatternExt, 1, 1)
End Function
End Class
Die öffentliche Funktion
FileNameMatch
der Klasse zerlegt den Dateinamen und das Suchmuster in die Bestandteile Name und Erweiterung und prüft beides separat auf eine Übereinstimmung durch Aufruf der privaten Funktion FindMatch
. Wenn beides dem angegebenen Suchmuster entspricht, wird True zurückgeliefert, sonst False.Die Hauptschleife in den Zeilen 18 bis 39 prüft die einzelnen Zeichen des Dateinamens und der Suchmaske auf Gleichheit. Ein Fragezeichen in der Suchmaske wird als Übereinstimmung gewertet.
Wenn ein Stern/Asterisk in der Suchmaske gefunden wird, wird zunächst geprüft, ob es sich um das letzte Zeichen der Suchmaske handelt. Wenn ja haben wir einen Treffer. Wenn nicht, ruft sich die Funktion in Zeile 33 selbst auf. Die Position des Zeichenzeigers in die Suchmaske wird für die neue Instanz um eins erhöht (
intIdxPattern + 1
). Die neue Instanz der Funktion sucht also nach Übereinstimmungen ab der gleichen Position im Dateinamen und der um eins erhöhten Position in der Suchmaske (hinter dem Asterisk).Wenn die neue Instanz keine Übereinstimmung gefunden hat, wird nach der Rückkehr der Positionszeiger
intIdxStr
in den Dateinamen um eins erhöht und, wenn die Abbruchbedingungen der Schleife nicht erfüllt sind, wieder eine Instanz der Funktion aufgerufen. intIdxPattern
hat dabei wieder den gleichen Wert wie beim letzten Aufruf. Die Schleife und damit, wegen Zeile 37, auch die Funktion bricht ab, wenn ein Treffer gefunden wurde oder alle Zeichen des Dateinamens überprüft wurden.Zeile 41 dient dazu den Fall zu erkennen, dass die Hauptschleife beendet wurde, weil nur einer der beiden Strings bis zum letzten Zeichen verarbeitet wurde. Dann liegt kein Treffer vor.
Zeile 42 ist für den Sonderfall, dass der Dateiname schon vollständig verarbeitet wurde, der Positionszeiger für die Suchmaske aber erst auf dem letzten Zeichen steht und dieses Zeichen ein Asterisk ist. Dann liegt ein Treffer vor (Ausnahme von der Ausnahme ).
Jetzt ein Beispiel zur Verwendung. Der obige Code muss in die Datei mit dem folgenden Code kopiert werden.
Const ForReading = 1
Const AsASCII = 0
intResult = 1
If WScript.Arguments.Count > 0 Then
arrFilesToOpen = Array()
Set objFNMatch = New clsFileNameMatch
Set objFSO = CreateObject("Scripting.FileSystemObject")
strMask = WScript.Arguments(0)
strFolder = objFSO.GetParentFolderName(strMask)
strFileName = objFSO.GetFileName(strMask)
If strFolder = "" Then strFolder = "."
If objFSO.FolderExists(strFolder) Then
Set objFolder = objFSO.GetFolder(strFolder)
Set colAllFiles = objFolder.Files
For Each objFile In colAllFiles
If objFNMatch.FileNameMatch(objFile.Name, strFileName) Then
ReDim Preserve arrFilesToOpen(UBound(arrFilesToOpen) + 1)
Set arrFilesToOpen(UBound(arrFilesToOpen)) = objFile
End If
Next
If UBound(arrFilesToOpen) >= 0 Then
For Each objFile In arrFilesToOpen
WScript.Echo "Datei " & objFile.Path & " wird geöffnet."
Set objStream = objFile.OpenAsTextStream(ForReading, AsASCII)
'
'Dateiinhalt verarbeiten
'
objStream.Close
Next
intResult = 0
Else
WScript.Echo "Keine Datei(en) gefunden."
End If
Else
WScript.Echo "Verzeichnis nicht gefunden."
End If
Else
WScript.Echo "Bitte eine Dateimaske übergeben!"
End If
WScript.Quit intResult
Aufruf:
cscript /nologo example.vbs D:\Texte\*.txt
In Zeile 19 wird eine Files Collection erzeugt, die alle Dateien des übergebenen Verzeichnisses enthält. Jeder Dateiname wird in Zeile 22 auf Übereinstimmung mit dem übergebenen Muster geprüft und bei einem Treffer das File Objekt in das Array
arrFilesToOpen
gespeichert, das dafür jedesmal dynamisch vergrößert wird.Über die Abfrage in Zeile 28 lässt sich feststellen, ob passende Dateien gefunden wurden. Es wird dann durch das Array iteriert, um mit jeder Datei die vorgesehene Aktion auszuführen. Im Beispiel wird der komplette Dateipfad angezeigt und die Datei zum Lesen geöffnet.
Gruß
Friemler
Bitte markiere auch die Kommentare, die zur Lösung des Beitrags beigetragen haben
Content-ID: 169161
Url: https://administrator.de/contentid/169161
Ausgedruckt am: 08.11.2024 um 15:11 Uhr
7 Kommentare
Neuester Kommentar
Servus Friemler,
ich trau mich ja fast nicht, dich zu fragen - aber machs trotzdem.
Das "Problem" ist mir auch schon mal aufgestossen und bei einer Struktur
liefert wie du oben schon aufgeführt hast ein
nicht das, was man erwarten würde.
Ein gerade für dich simples
bringt aber doch genau das, was gewünscht - war dir das zu einfach?
Trotzdem, oder gerade weil - 1.001 Dank für die Mühe.
ich trau mich ja fast nicht, dich zu fragen - aber machs trotzdem.
Das "Problem" ist mir auch schon mal aufgestossen und bei einer Struktur
C:\>dir /b C:\script\friemler\*
test.cmd
test.txt
test1.txt
test2.txt
liefert wie du oben schon aufgeführt hast ein
C:\>dir /b C:\script\friemler\t??t?.txt
test.txt
test1.txt
test2.txt
Ein gerade für dich simples
C:\>dir /b C:\script\friemler | findstr /i "t..t..txt"
test1.txt
test2.txt
Trotzdem, oder gerade weil - 1.001 Dank für die Mühe.
Aloha T-Mo,
stimmt, das hatte ich letztens erst, habe nach
greetz André
... ob das in dem anderen Thread dann so funktioniert ... ??.??.????.doc ... ich bin gespannt, der TE gibt ja kein Feedback
edit: ??.??.????.doc findet auch Dateien mit 9.3.11.doc ... also nicht nur eine Stelle, sondern sogar mehrere Stellen weniger als angegeben ...
stimmt, das hatte ich letztens erst, habe nach
?.*
gesucht und bekam dann auch als Ergebnis: ._descrip.mms
ich wunderte mich schon ... aber gut, dein simpler Test bestätigt es ja, dass dies ein immerwährender bug ist ... wobei das mit dem Punkt nach dem Fragezeichen zusammenhängen muss, denn in anderen Positionen kann ich das Phänomen nicht produzieren.greetz André
... ob das in dem anderen Thread dann so funktioniert ... ??.??.????.doc ... ich bin gespannt, der TE gibt ja kein Feedback
edit: ??.??.????.doc findet auch Dateien mit 9.3.11.doc ... also nicht nur eine Stelle, sondern sogar mehrere Stellen weniger als angegeben ...
Hallo Friemler.
Da hast du eine Menge Hirnschmalz reingesteckt. Vermutlich einfacher wäre die Verwendung von Regulären Ausdrücken gewesen.
Diese (nur ansatzweise getestete) Funktion sollte es eigentlich schon tun:
Grüße
rubberman
<Edit:
Da hast du eine Menge Hirnschmalz reingesteckt. Vermutlich einfacher wäre die Verwendung von Regulären Ausdrücken gewesen.
Diese (nur ansatzweise getestete) Funktion sollte es eigentlich schon tun:
Function FileNameMatch(strFileName, byVal strPattern)
arrSpecialChars = Array("^", "$", "+", ".", "(", ")", "{", "}", "[", "]")
For Each strSpecialChar In arrSpecialChars
strPattern = Replace(strPattern, strSpecialChar, "\" & strSpecialChar)
Next
strPattern = Replace(strPattern, "?", ".")
strPattern = "^" & Replace(strPattern, "*", ".*") & "$"
Set objRegEx = New RegExp
objRegEx.IgnoreCase = True
objRegEx.Pattern = strPattern
FileNameMatch = objRegEx.Test(strFileName)
Set objRegEx = Nothing
End Function
Grüße
rubberman
<Edit:
byVal
ergänzt />
Hallo Friemler.
ByVal ist die bessere Variante, da auch eine Hilfsvariable global vordefiniert sein könnte und somit verändert würde.
Danke für den Hinweis, ich ändere oben.
Grüße
rubberman
PS:
d'accord
Zitat von @Friemler:
Allerdings muss
oder einer funktionsinternen Hilfsvariable zugewiesen werden (besser), mit der dann weitergearbeitet wird
Allerdings muss
strPattern
ByVal übergebenoder einer funktionsinternen Hilfsvariable zugewiesen werden (besser), mit der dann weitergearbeitet wird
ByVal ist die bessere Variante, da auch eine Hilfsvariable global vordefiniert sein könnte und somit verändert würde.
Danke für den Hinweis, ich ändere oben.
Grüße
rubberman
PS:
d'accord