M
Mial
Threadstarter
- Dabei seit
- 14.07.2004
- Beiträge
- 2.132
Hi
Die Systemfunktionen des Kommandointerpreters sind hilfreich und nuetzlich fuer alle moeglichen Aufgaben, aber leider schlecht bis gar nicht dokumentiert. Das stellt viele Anwender immer wieder vor scheinbar unloesbare Probleme, wie z.B. der Thread "Dateien per Batch löschen, die älter als zwei Tage sind" zeigt. Deshalb wage ich an diesem Beispiel mal einen kleinen Versuch, ein wenig Licht ins Dunkel der Batchprogrammierung zu bringen.
Das fertige Programm ist natuerlich auch in einem Zip-File zum Download am Ende dieses Postings angefuegt.
Dieses Batchprogramm soll also ausgewaehlte Dateien loeschen und unter allen (westlichen) Versionen von win XP und Win 2003 lauffaehig sein. Egal also, ob jemand z.B. eine (US-)englische oder eine deutsche Win Version verwendet.
Die gewaehlten Dateien sollen ein bestimmtes Alter erreicht haben, welches frei bestimmbar ist. Die zu pruefenden Verzeichnisse sollen frei waehlbar sein. Ebenso die Dateinamen und Dateitypen. Die Verarbeitung und Auswertung von Wildcards (aka Platzhaltern - also das Sternchen * und das Fragezeichen ?) ist deshalb angezeigt. Anwendungskomfort und Sicherheit gegen Fehlbedienung sollen auf keinen Fall aus dem Blick gelassen werden sondern unser wichtigstes Ziel sein.
Da es um eine kritische Aufgabe geht (das Loeschen von Dateien) ist eine ausgekluegelte Fehlerkontrolle unabdingbar und vor dem endgueltigen Ausfuehren des Auftrags sollte eine Ueberpruefung durch den Anwender moeglich sein.
Da vorher i.d.R. nicht bekannt ist, wieviele Dateien zu bearbeiten sind, es koennten 3, 30 oder 300 sein, ist das Anlegen, Anzeigen und Abarbeiten einer Auftragsliste zweckmaessig.
Es ist immer praktisch, auf vorhandene Funktionen des Betriebssystems zurueck zu greifen. Das erspart das Programmieren eigener Routinen. Das Anlegen einer Liste potentieller Loeschkandidaten kann darum z.B. der DIR-Befehl fuer uns uebernehmen. Wir passen seine Ausgabe dann einfach an unsere Erfordernisse an.
Zunaechst muessen wir aber mal ermitteln, in welchem Verzeichnis und nach welchen Dateinamen/-typen wir denn suchen sollen. Also schauen wir mal, was der Anwender denn dazu eingegeben hat und ordnen das passenden Variablen zur Weiterverarbeitung zu ...
Vor jedem Verarbeitungsschritt sollten wir dafuer Sorge tragen, das Variable sich immer in einem definiertem Zustand befinden, um Fehler zu vermeiden und dadurch gelegentlich hoechst unangenehmen Ueberraschungen vorzubeugen (SET xy=).
Teilen wir nun die Eingabe auf und ordnen sie sinnvollen Variablen zu. Dazu bedienen wir uns der Bequemlichkeit halber einer Systemfunktion.
Gibt es wenigstens eine, auf die Eingabe passende Datei (IF EXIST), machen wir weiter (GOTO job). Wenn nicht, geben wir eine entsprechende Meldung aus, raeumen auf und beenden die Batchverarbeitung.
Existiert mindestens eine, auf die Eingabe passende Datei, sehen wir uns die Eingabe einmal naeher an.
Handelt es sich bei der Eingabe um ein gueltiges Verzeichnis, ohne Wildcards im Pfadnamen, gibt Win den Pfad aus und setzt die Dateierweiterung als Punkt. In dem Fall koennen wir direkt mit dem Erstellen der Dateiliste beginnen (GOTO get_file_list).
Sind Wildcards im Pfadnamen der Eingabe, setzt Win den Pfad, der Name des ersten passenden Unterverzeichnisses wird als Dateiname zurueckgegeben und die Namenserweiterung bleibt leer.
In dem Fall koennen wir dann eine Liste moeglicher Unterverzeichnisse erstellen und abarbeiten oder wir verwenden zur weiteren Verarbeitung einfach das erste gefundene Verzeichnis, das auf die Anwendereingabe passt.
Ich habe mich fuer letzteres entschieden, in der Annahme, das zwar oft verschiedene Dateinamen/-typen gleichzeitig zur Verarbeitung gefordert werden, seltener aber mehrere verschiedene Verzeichnisse nebeneinander zugleich bearbeitet werden sollen. Ich habe mir also einen Verarbeitungsschritt erspart.
Wer moechte, kann den Batch aber natuerlich um entsprechende Routinen ergaenzen ... vielleicht mache ich es irgendwann einmal auch selbst ... : )
Setzen wir also den Pfad auf das gefundene Verzeichnis, wenn es denn eines ist (IF NOT EXIST dann natuerlich nicht) und wenden wir uns dann gewuenschten Dateinamen/-typen zu.
Wir verwenden den FIND-Befehl, um festzustellen, ob ueberhaupt Wildcards (Platzhalter fuer Teile von Dateinamen oder Pfadangaben) eingegeben wurden. Wenn nein, koennen wir direkt mit der Erstellung der Dateiliste fortfahren (GOTO get_file_list).
Wenn doch, muessen wir die Eingabe zerlegen.
Wurde eine Laufwerksbezeichnung eingegeben ?
Wurde eine Pfadangabe gemacht ?
Wieder findet auch hier der FIND-Befehl Verwendung. Um die Angaben zwischen den gefundenen Trennzeichen zu erhalten, rufen wir eine Subroutine auf und teilen ihr beim Aufruf mit, nach welchem Trennzeichen gefiltert werden soll (CALL :finde_Trennzeichen x).
Da wir noch nicht wissen, wieviele gueltige Trennzeichen in der Eingabe enthalten sind, arbeitet die Routine sie einfach rekursiv von links nach rechts ab, bis das letzte Segment gefunden ist. Dieses bleibt dann als Ergebnis zur Weiterverarbeitung in der Variablen stehen.
TOKENS gibt an, welche Elemente der aktuellen Kette wir haben moechten. In diesem Fall jeweils das erste Element (1) und alles (*) NACH dem Trennzeichen (zur Rekursion) in je einer eigenen Variablen.
DELIMS (Delimitations = Abgrenzungen) gibt an, bei welchen gefundenen Trennzeichen die Zeichenkette aufgespalten werden soll. Es wird in diesem Fall jeweils beim Aufruf der Subroutine im Parameter %1 uebergeben.
Nun haben wir also einen in der Eingabe enthalten Dateinamen gefunden. Enthaelt er Platzhalter? Dann werden diese in die Variable zur Dateisuche uebernommen.
Endlich koennen wir die Dateiliste erstellen. Dazu leiten wir die Ausgabe des DIR-Befehls in eine Datei. Es ist dabei immer guter Stil, dem Anwender so ungefaehr mitzuteilen, was gerade passieren soll, statt ihn vor dem leeren Bildschirm dumm 'rum sitzen zu lassen ...
Auch hier findet erneut der FIND-Befehl Anwendung, um die relevanten Ergebniszeilen zu erhalten. Zuerst filtern wir die Zeilen mit den Verzeichniseintraegen heraus. Die brauchen wir hier nicht (/V).
Dann suchen wir die Angaben zu evtl. gefundenen Dateien, in dem wir nach dem Trennzeichen der Zeitangabe suchen (":"). Das ist im Gegensatz zum Datumstrennzeichen naemlich in allen westlichen Win Versionen gleich ... : )
Warum verwenden wir beim DIR-Befehl nicht gleich das Attribut /A-D ?
Ausgabe aller Eintraege, die nicht das Directory-Attribut tragen ?
Weil dann eine Fehlermeldung am Bildschirm ausgegeben wuerde, wenn das angefragte Verzeichnis keine Dateien enthaelt. Das liesse sich aber nicht abfangen und man koennte im Batchablauf nicht darauf reagieren, da die internen Kommandos den ERRORLEVEL nicht setzen/veraendern. Deshalb der kleine "Umweg" ueber den FIND-Filter ; )
Wir haben jetzt also eine Verzeichnisliste namens $Dummy1.txt stilgerecht im Verzeichnis fuer temporaere Dateien (uebernommen aus der Systemvariablen %TEMP%). Existiert kein TEMP-Verzeichnis (unwahrscheinlich) oder wurde die TEMP-Variable geloescht, wird die Liste im aktuell gueltigen Verzeichnis erstellt. Dafuer sorgt der kleine Punkt zwischen %TEMP% und dem Backslash. : D
Diese Liste muessen wir nun noch ein wenig bearbeiten und vor allem auswerten.
Wir benoetigen von den in Frage kommenden Dateien nur das Datum und den Namen ...
... das ist erste Element (%%a) der jeweiligen Zeile und der ganze Rest NACH dem dritten Element (%%c). Naemlich der Dateiname samt Namenserweiterung und eventuell enthaltener Leerzeichen (!) im Dateinamen.
Die ersten 3 Zeilen ueberspringen wir dabei (SKIP). Sie enthalten nur Angaben zur Festplatte und zum durchsuchten Pfad.
Ausserdem klaeren wir gleich die Frage: Wurde ueberhaupt eine Datei im abgefragten Verzeichnis gefunden ?
Um nun das Dateialter zu pruefen muessen wir erst mal das Loeschdatum berechnen. Also das Datum, mit dem wir vergleichen wollen, ob die gefundene Datei alt genug ist, um sie loeschen zu wollen.
Zunaechst brauchen wir also das aktuelle Datum. Das steht in der Systemvariablen DATE.
Aber in welchem Format steht es dort? Schliesslich ist bei den Anwendern nicht nur die deutsche Version von Win im Einsatz ...
Also muessen wir erstmal herausfinden, in welchem Format es auf dem gegenwaertigem PC vorliegt. Sehen wir nach, was im Augenblick in der Systemvariablen DATE steht ...
Wenn wir Glueck haben, ist nicht gerade Neujahr oder der erste April etc. sondern ein Tag zwischen dem 13. und dem Monatsende. Warum?
Ganz einfach. Der Wert fuer den Monat kann nur Werte von 1 bis 12 aufweisen. Ist der mittlere Teil der DATE-Variablen groesser 12 kann demzufolge nur Form 2 vorliegen - MM TT JJJJ.
Ist der linke Teil der Variablen groesser 12 handelt es sich um Form 1 - TT MM JJJJ oder Form 3 - JJJJ MM TT.
Ist dieser Teil also groesser 31, liegt in jedem Fall Form 3 vor.
Was aber, wenn der Tag im Bereich vom 1. bis zum 12. eines Monats liegt und Form 3 ausgeschlossen ist ?
Dann muessen wir die Moeglichkeiten austesten ...
Wieso +12 ? Einfach. An diesem Punkt des Programmablaufs ist Form 3 bereits ausgeschlossen. Es geht nur noch darum, zwischen Form 1 und Form 2 zu unterscheiden - also TTMM oder MMTT.
Fangen wir den Check am besten mit dem im deutschsprachigem Raum meistverbreitetem Format an und setzen die Position mit dem vermutetem Tag um den Wert 12 herauf. Denn an dieser Stelle des Programms liegt der Wert fuer den aktuellen Tag des Datums irgendwo zwischen 1 und 12 (jaja, ich weis - genau genommen zwischen 0 und 13).
Indem wir nun 12 hinzu addieren, erhoehen wir den Wert also mindestens auf die Zahl 13 und erreichen damit auf jeden Fall einen Wert groesser als der hoechstzulaessige Wert fuer Monat.
Wie schon erwaehnt, geben interne Kommandos leider keinen ERRORLEVEL zurueck. Deshalb muessen wir den Erfolg oder Misserfolg der Aktion durch nochmaliges Ueberpruefen des Wertes der DATE-Variable feststellen ...
Zur Ueberpruefung brauchen wir diesmal nur den linken/ersten Teil der Variablen (TOKENS=1). Hatten wir mit dem DATE-Befehl Erfolg, so wird der Tag jetzt groesser 12 angezeigt und wir haben die richtige Form ermittelt. TT MM JJJJ.
Natuerlich stimmt nun das Datum nicht mehr, da wir den Wert fuer den Tag erhoeht hatten. Das muessen wir korrigieren und wieder zuruecksetzen.
Gut, das wir den Wert fuer Tag um genau 12 erhoeht hatten, statt einfach eine beliebige Zahl zwischen 12 und 30/31 (28 ; ) Februar) zu setzen. So koennen wir den richtigen Wert fuer Tag ganz einfach wieder herstellen, indem wir 12 abziehen.
Selbst wenn dies Programm um Mitternacht herum laeuft und mittlerweile gerade ein neuer Tag begonnen hat, ist das Ergebnis dann immer noch das richtige ... : D
Hatten wir mit dem DATE-Befehl keinen Erfolg, so ist der Wert an der linken Position der DATE-Variablen unveraendert kleiner als 13. - Bleibt fuer das Format also nur noch Form 2 - MM TT JJJJ.
Ueberpruefen wir das, indem wir die mittlere Position um 12 erhoehen ...
Durch die IF Abfragen ist es egal, ob wir den linken Wert zuruecksetzen muessen oder zwecks abschliessender Pruefung den mittleren Wert erhoehen wollen. Der Effekt wird immer der richtige sein ... : )
nur abfragen muessen wir den (mittleren) Wert nochmal ... (TOKENS=2)
Spaetestens jetzt muss das Format einfach bestaetigt sein. MM TT JJJJ.
Wenn nicht, haben wir ein riesengrosses Problem ... :O
Nachdem wir nun das Datumsformat kennen, koennen wir das
Nanu - fragen wir hier das aktuelle Datum nicht ein zweites Mal direkt nacheinander ab ? Haben wir das nicht eben schon getan ? - Nein. Im vorhergehenden Abschnitt waren TAG, MONAT und JAHR nur Platzhalter fuer die Werte von %%a %%b und %%c ! Das richtige Format kannten wir ja noch nicht. Erst jetzt ordnen wir die Variablen entsprechend dem ermitteltem Datumsformat richtig zu, indem wir die passende Subroutine aufrufen ... (CALL :datum_%dform%)
Der Vergleichswert fuer das Loeschdatum ergibt sich aus dem aktuellen Datum und dem Wert fuer die Tage, die eine Datei mindestens alt sein soll, indem wir die beiden einfach von einander abziehen. Einfach ? Naja ...
Was geschieht hier ?
Zunaechst vergleichen wir mal, ob der Wert fuer %tag% kleiner oder gleich dem Wert ist, der der gewuenschten Zahl an Tagen entspricht, die fuer das Mindestalter der zu pruefenden Dateien vorgesehen ist.
Ist das der Fall, addieren wir die Anzahl der Tage der Vormonats zum Wert fuer %tag% hinzu. Vom Wert fuer %monat% ziehen wir natuerlich 1 ab.
Aber die Monate haben unterschiedlich viele Tage ? - Kein Problem. Addieren wir zunaechst auf jeden Fall mal 31. Sieben von zwoelf Monaten des Jahres haben 31 Tage. Mehr als die Haelfte decken wir damit also schon mal ab.
Dann ziehen wir 1 ab, wenn der Vormonat nur 30 Tage hat. Das kommt im Jahr genau nur viermal vor. Aber wieso steht da 5 7 10 und 12 (Mai Juli Oktober Dezember) ? Die haben doch 31 Tage ... Klar - als aktuell gesetzter Monat ... aber ihr jeweiliger Vormonat hat nur 30 Tage ... und DEN loesen wir ja auf ... : )
Dann fragen wir ab, ob der aktuell gesetzte Monat der Maerz ist. Ist das der Fall, ziehen wir fuer den Februar eben 3 Tage von den zuvor zugefuegten 31 ab. Den 29. Februar schenken wir uns und berechnen den Feber immer mit 28 Tagen.
Jetzt (!) ziehen wir vom Wert fuer %monat% 1 ab. Verrechnen wir dadurch zufaellig gerade den Januar und der Wert fuer %monat% faellt auf 0, muessen wir natuerlich vom Wert fuer %jahr% auch 1 abziehen und den Wert fuer %monat% dann auf 12 setzen.
Ist der Wert fuer %tag% jetzt groesser, als der Wert fuer die Anzahl Tage, die eine Datei aelter sein soll, so ziehen wir sie voneinander ab. Wenn nicht, wiederholen wir den Durchgang.
Nun sorgen wir noch dafuer, das die Werte fuer %tag% und %monat% eine fuehrende Null erhalten, falls sie kleiner sind als 10. - Warum ?
Nun - das Ergebnis der Subroutine wird nach dem Ruecksprung zum Wert fuer die Variable %datum% zusammengesetzt in der Form JJJJMMTT.
Das ergibt einen einmaligen und unverwechselbaren Vergleichswert fuer das Loeschdatum.
Ein mathematischer Vergleich in der Form JJJJMMTT kleiner/gleich JJJJMMTT wird IMMER das richtige Ergebnis liefern.
Beim mathematischem Vergleich zweier Zahlen z.B. nach dem Muster TTMMJJJJ kleiner/gleich TTMMJJJJ waere das nicht der Fall ...
Aus diesem Grund liessen wir uns vom DIR-Befehl die Jahreszahl auch vierstellig ausgeben. Damit bei einem Vergleich mit Dateien z.B. von 1999 dann auch das richtige Ergebnis zu Stande kommt ... 19990101 kleiner/gleich 20060201 und nicht etwa 990101 kleiner/gleich 060201 ... na ? gut aufgepasst ? ; )
Nun koennen wir das
Auch hier richten wir uns natuerlich wieder nach dem ermitteltem Datumsformat ... und hier kommt nun der eben schon besprochene Vergleich in Form JJJJMMTT LESS OR EQUAL JJJJMMTT zum Tragen ...
und auch der Pfadname sollte gleich mit hinzugefuegt werden, um Irrtuemer oder Verwechslungen auszuschliessen.
Warum nicht einfach nur auf KLEINER vergleichen ? Damit wir auf Wunsch auch Dateien loeschen koennen, wenn sie nur einen Tag alt sind, also das Datum von gestern tragen und der Vergleichswert %datum% ist ja zumindest das Datum vom Vortag ...
Sehen wir nun mal nach, auf wieviele Dateien das Kriterium Loeschalter zutrifft ...
Vom Ergebnis ziehen wir 1 ab. Immerhin haben wir ja in jedem Fall eine leere Datei mit dem ECHO-Befehl erstellt. Das erzeugt zumindest eine Leerzeile, auch wenn im Datumsvergleich keine zutreffende Datei ermittelt wurde.
Und diese Leerzeile wollen wir ja nun nicht unbedingt als Erfolg mit werten ...
Der Parameter /A zum Befehl SET bewirkt, dass das Argument arithmetisch (mathematisch, nummerisch) ausgewertet wird, bevor es der gewuenschten Variablen als Wert zugewiesen wird.
Keine passende Datei gefunden ? Dann geben wir eine entsprechende Meldung aus ...
doch was gefunden ? Dann sollten wir dem Anwender auch anzeigen, was wir gefunden haben ...
dazu verwenden wir den TYPE-Befehl. Der kann die erstellte Liste fuer uns anzeigen. Fuer den Fall, das sie sehr umfangreich ist, sorgt der MORE-Befehl dafuer, das der Anwender auch alles mitbekommt ...
Nun sollten wir dem Anwender aber auch die Chance geben, die Liste nach der Sichtung zu bestaetigen oder ggf. zu verwerfen.
Antwortet der Anwender mit 'n' oder 'N' wird die Liste verworfen. Ein Loeschvorgang findet dann nicht statt.
Hat der Anwender die Liste bestaetigt, wuenscht das Loeschen der angezeigten Dateien und hat also mit 'j' oder 'J' geantwortet, tun wir den vorletzten Schritt ...
Erst jetzt (!) erstellen (!) wir tatsaechlich, basierend auf der bestaetigten Loeschliste, eine Routine, die den eigentlichen Loeschvorgang vornimmt.
Der Inhalt der Variable %cnt% bestimmt, ob zusaetzlich VOR dem Loeschen JEDER einzelnen Datei eine systemeigene Sicherheitsabfrage erfolgen soll ...
Hinweis:
Wer dieses Batchprogramm erst einmal voellig gefahrlos testen moechte, ersetzt bitte in der fertigen Batch-Datei zunaechst mal das Befehlswort CALL aus dem naechsten Abschnitt in der Zeile SET cnt=CALL durch das Befehlswort TYPE ...
SET cnt=TYPE
So kann in aller Ruhe betrachtet werden, wie dies Batch-Programm arbeitet ...
Das Erzeugen von zunaechst einer txt-Datei (:write_kill_list), die erst unittelbar vor der Ausfuehrung in eine bat-Datei umbenannt wird (REN), die dann ausgefuehrt, den eigentlichen Loeschjob uebernimmt, ist sozusagen ein letztes doppeltes Sicherheitsnetz.
Bleibt, aus welchen ungluecklichen Umstaenden auch immer (Systemabsturz, Abbruch, Stromausfall, ...) eine txt-Datei im Temp-Verzeichnis zurueck, kann sie normal nicht viel Schaden anrichten. Ein bat-File koennte immer noch von einem DAU oder einfach aus Unkenntnis (Kinder, Ehegatten, Arbeitskollegen, ...) durch fehlerhaftes Anklicken versehentlich gestartet statt betrachtet werden und evtl. nachtraeglich noch unerwuenscht Schaden anrichten.
Nach dem Job sollte natuerlich auch eine Vollzugsmeldung erfolgen. Die erfolgt u.a. durch die abschliessende Zeile
... ECHO %count% Datei(en) geloescht. ...
im erzeugten bat-File (siehe Abschnitt :do_it).
Die 'statistics' dienen der Information. Man kann sich hier nochmal vor Augen fuehren, was man denn da gerade eigentlich gemacht hat. : D
Der Befehl SHIFT befoerdert dann den aktuell gerade abgearbeiteten Parameter von seinem Platz. Gibt es weitere Angaben, wird der Job mit den naechsten Parametern wiederholt. Dem Batch-Programm koennen also mehrere abzuarbeitende Eingaben beim Aufruf uebergeben werden.
z.B.: older.bat *.bac briefe\*.ba? vorlagen\*.tmp *.tst Kopie*.doc
oder: older.bat "meine Gedichte\*.ba?" "\alte texte\Kopie (?) von *"
Bitte daran denken: Dateiangaben, die Leerzeichen enthalten, muessen in Anfuehrungszeichen gesetzt werden.
Nach dem Job (und natuerlich auch bei jedem vorzeitigem kontrolliertem Beenden des Programms) sollte immer auch aufgeraeumt werden ...
Wer das Programm erstmal ausprobieren will, kann vor die letzten beiden Zeilen ein REM setzen und sich nach einem Durchlauf ansehen, was en detail in den temporaeren Dateien zwischengespeichert ist und wie es jeweils weiterverarbeitet wurde ...
Das sollte es jetzt gewesen sein.
Aber ist das Programm schon fertig ?
Naja - Am Anfang eines guten Programms sollte immer stehen, was es macht und unter welchen Bedingungen es das tut. Also ...
Die Befehlszeile @ECHO OFF
am Anfang einer Batchdatei bewirkt, das waehrend der Abarbeitung nur erwuenschte (sinnvolle) Ausgaben am Bildschirm erscheinen. Fehlt die Zeile oder ist sie auskommentiert (z.B. durch ein REM ), so wird jede abzuarbeitende Zeile der Datei auch am Bildschirm ausgegeben. Das ist aber allenfalls als Hilfsmittel zur Fehlersuche sinnvoll ...
Wir listen die verwendeten Variablen auf und sorgen fuer freien Blick auf die Programmmeldungen. (CLS - clear screen).
Und dann pruefen wir gleich mal, ob beim Programmaufruf ueberhaupt Parameter uebergeben wurden. Ist das nicht der Fall, geben wir eine Kurz-Anleitung zum Programmaufruf mit den moeglichen und wahlfreien Angaben aus ...
Hat der Anwender die Batchdatei mit den notwendigen Angaben gestartet, bereiten wir das Programm auf sein Wirken vor.
Wuenscht der Anwender zusaetzlich VOR dem Loeschen JEDER einzelnen Datei eine systemeigene Sicherheitsabfrage oder wurde der Parameter /noq (no query) gesetzt, weil er es nicht wuenscht ?
Da es sich nur um drei Zeichen fuer eine i.d.R. vermutlich nicht gewuenschte Operation handelt, beruecksichtigen wir auch gleich mal evtl. Tipfehler bei der Eingabe, die in Zusammenhang mit der Shifttaste stehen ... : ( : )
die REMarks erklaeren es schon ...
und wenn angegeben, lesen wir dann aus, wie alt die zur nachfolgenden Eingabe passenden Dateien mindestens sein sollen, um geloescht zu werden.
Enthaelt ein Argument als Wert (versehentlich) eine Zahl mit fuehrenden Nullen (z.B. 011) so wird es bei einer Variablen-Zuweisung mit dem Parameter /A als Oktalwert interpretiert. Bei einem Tipfehler in der Eingabe fuehrt das natuerlich zu kuriosen und unerwuenschten Ergebnissen. Deshalb entfernen wir vorsichtshalber evtl. vorhandene fuehrende Nullen.
Ausserdem findet natuerlich auch eine Gueltigkeitspruefung des uebergebenen Wertes statt.
Da das Label
arameter auch als Wieder-Einsprung-Punkt nach einem Durchlauf gedacht ist, kann bei Angabe mehrerer Dateitypen oder Verzeichnisnamen hintereinander, jeweils fuer jede Angabe wahlweise ein eigenes Mindestalter vorgegeben werden.
Bsp.: older.bat M*.txt /d 30 *.bac /d 20 "Kopie (?) von *.doc" d:\texte\*.bak ...
Wird nicht erneut ein Mindestalter angegeben, so wird die jeweils letzte Angabe verwendet.
Wird gar kein Mindestalter angegeben, so wird die Vorgabe aus dem vorstehenden Abschnitt uebernommen.
Damit sollten jetzt alle notwendigen Module erstellt und erlaeutert sein.
Die Anordnung der Module in der fertigen Batch-Datei im Anhang ist fuer die Abarbeitung durch einen Kommando-Interpreter angepasst. Die Reihenfolge entspricht somit nicht dem seriellen Programmablauf sondern dient der Sprungoptimierung. Evtl. zeitrelevante Routinen befinden sich deshalb am Anfang der Datei.
In einer Compiler-Version wuerde z.B. die Modulanordnung wiederum deutlich anders aussehen. Oder die im Batch mehrfach verwendete Befehlszeile
ECHO %target% | FIND "%irgendwas%" > NUL
Sie wuerde dann z.B. in einer Subroutine landen. : D
Dennoch wurde eine gewisse Strukturierung der Uebersichtlichkeit wegen beibehalten. REM Zeilen dienen der Erlaeuterung, Leerzeilen der Gliederung. Die Erfahrung zeigt, das nach einer gewissen Zeit sonst nur erschwert nachvollziehbar ist, was in den Programmzeilen durchgefuehrt wird. ; )
Natuerlich waere eine weitere Optimierung des Codes moeglich. Sie ginge aber vermutlich zu Lasten der Struktur und macht nur fuer wirklich alte Computer evtl. Sinn.
Meine Absicht war es, ein uebersichtliches, zweckgerichtetes, komfortables und moeglichst 'sicheres' Batchprogramm zu schreiben. Gerade auch beim sensiblen Vorgang des Datei loeschens ... Lieber eine Pruefroutine zu viel, als ein ungewollter Datenverlust. Deshalb z.B. auch die staendige Ueberpruefung und die streng definierte Zuweisung an variable Platzhalter.
Jegliche Aenderung, Erweiterung oder 'Optimierung' an diesem Programmcode ist selbstredend erlaubt, geschieht aber natuerlich ausdruecklich auf eigenes Risiko. : )
cu
m,-
Die Systemfunktionen des Kommandointerpreters sind hilfreich und nuetzlich fuer alle moeglichen Aufgaben, aber leider schlecht bis gar nicht dokumentiert. Das stellt viele Anwender immer wieder vor scheinbar unloesbare Probleme, wie z.B. der Thread "Dateien per Batch löschen, die älter als zwei Tage sind" zeigt. Deshalb wage ich an diesem Beispiel mal einen kleinen Versuch, ein wenig Licht ins Dunkel der Batchprogrammierung zu bringen.

Das fertige Programm ist natuerlich auch in einem Zip-File zum Download am Ende dieses Postings angefuegt.

Dieses Batchprogramm soll also ausgewaehlte Dateien loeschen und unter allen (westlichen) Versionen von win XP und Win 2003 lauffaehig sein. Egal also, ob jemand z.B. eine (US-)englische oder eine deutsche Win Version verwendet.
Die gewaehlten Dateien sollen ein bestimmtes Alter erreicht haben, welches frei bestimmbar ist. Die zu pruefenden Verzeichnisse sollen frei waehlbar sein. Ebenso die Dateinamen und Dateitypen. Die Verarbeitung und Auswertung von Wildcards (aka Platzhaltern - also das Sternchen * und das Fragezeichen ?) ist deshalb angezeigt. Anwendungskomfort und Sicherheit gegen Fehlbedienung sollen auf keinen Fall aus dem Blick gelassen werden sondern unser wichtigstes Ziel sein.
Da es um eine kritische Aufgabe geht (das Loeschen von Dateien) ist eine ausgekluegelte Fehlerkontrolle unabdingbar und vor dem endgueltigen Ausfuehren des Auftrags sollte eine Ueberpruefung durch den Anwender moeglich sein.
Da vorher i.d.R. nicht bekannt ist, wieviele Dateien zu bearbeiten sind, es koennten 3, 30 oder 300 sein, ist das Anlegen, Anzeigen und Abarbeiten einer Auftragsliste zweckmaessig.
Es ist immer praktisch, auf vorhandene Funktionen des Betriebssystems zurueck zu greifen. Das erspart das Programmieren eigener Routinen. Das Anlegen einer Liste potentieller Loeschkandidaten kann darum z.B. der DIR-Befehl fuer uns uebernehmen. Wir passen seine Ausgabe dann einfach an unsere Erfordernisse an.
Zunaechst muessen wir aber mal ermitteln, in welchem Verzeichnis und nach welchen Dateinamen/-typen wir denn suchen sollen. Also schauen wir mal, was der Anwender denn dazu eingegeben hat und ordnen das passenden Variablen zur Weiterverarbeitung zu ...
Code:
:split_filename
REMMuster "Lw:\Pfad\bsp. pfad\test abc.txt"
FOR %%a IN (pfad name ext target) DO SET %%a=
FOR /F %%a IN ("%~1") DO (
SET "pfad=%~dp1"
SET "name=%~n1"
SET "ext=%~x1"
SET "target=%~1"
)
IF EXIST "%pfad%%name%%ext%" GOTO job
GOTO no_file
REM /F- verwende die Befehlserweiterungen fuer
REMDateinamen und Zeichenketten-Operationen
REM%~1- entfernt alle umschliessenden Anfuehrungszeichen von %1
REM%~f1 - gibt einen vollstaendigen Dateinamen fuer %1 aus
REM%~d1 - gibt den Laufwerkbuchstaben von %1 aus
REM%~p1 - gibt den Pfad von %1 aus
REM%~n1 - gibt den Dateinamen von %1 aus
REM%~x1 - gibt die Dateierweiterung von %1 aus
REM%~a1 - gibt die Dateiattribute von %1 aus
REM%~t1 - gibt Datum und Zeit von %1 aus
REM%~z1 - gibt die Dateigroesse von %1 aus
Teilen wir nun die Eingabe auf und ordnen sie sinnvollen Variablen zu. Dazu bedienen wir uns der Bequemlichkeit halber einer Systemfunktion.
Gibt es wenigstens eine, auf die Eingabe passende Datei (IF EXIST), machen wir weiter (GOTO job). Wenn nicht, geben wir eine entsprechende Meldung aus, raeumen auf und beenden die Batchverarbeitung.
Code:
:no_file
@ECHO OFF
ECHO.
ECHO--------------------------------------------------------
ECHOHinweis-folgende Datei existiert nicht:
ECHO.
ECHO"%pfad%%name%%ext%"
ECHO.
ECHOPfad- und Dateinamen, die Leerzeichen enthalten,
ECHOmuessen in Anfuehrungszeichen gesetzt werden.
ECHO--------------------------------------------------------
ECHO.
ECHO Das Programm wird jetzt geordnet beendet.
ECHO Bitte eine beliebige Taste druecken ...
PAUSE > NUL
GOTO clean-up
Code:
:job
IF []==[%ext%] IF NOT EXIST "%pfad%%name%\" SET ext=.
IF []==[%ext%] IF ""=="%name%" SET ext=.
IF []==[%ext%] SET pfad=%pfad%%name%\
IF []==[%ext%] SET name=.
IF "."=="%ext%" GOTO get_file_list
IF "."=="%name%" GOTO get_file_list
Sind Wildcards im Pfadnamen der Eingabe, setzt Win den Pfad, der Name des ersten passenden Unterverzeichnisses wird als Dateiname zurueckgegeben und die Namenserweiterung bleibt leer.
In dem Fall koennen wir dann eine Liste moeglicher Unterverzeichnisse erstellen und abarbeiten oder wir verwenden zur weiteren Verarbeitung einfach das erste gefundene Verzeichnis, das auf die Anwendereingabe passt.
Ich habe mich fuer letzteres entschieden, in der Annahme, das zwar oft verschiedene Dateinamen/-typen gleichzeitig zur Verarbeitung gefordert werden, seltener aber mehrere verschiedene Verzeichnisse nebeneinander zugleich bearbeitet werden sollen. Ich habe mir also einen Verarbeitungsschritt erspart.

Wer moechte, kann den Batch aber natuerlich um entsprechende Routinen ergaenzen ... vielleicht mache ich es irgendwann einmal auch selbst ... : )
Setzen wir also den Pfad auf das gefundene Verzeichnis, wenn es denn eines ist (IF NOT EXIST dann natuerlich nicht) und wenden wir uns dann gewuenschten Dateinamen/-typen zu.
Code:
REMWildcards (*?) und Trennzeichen (:\) verarbeiten
SET cnt=
REMWildcards in der Eingabe ?
SET count=2
ECHO %target% | FIND "*" > NUL
IF ERRORLEVEL 1 SET /A count-=1
ECHO %target% | FIND "?" > NUL
IF ERRORLEVEL 1 SET /A count-=1
IF %count%==0 GOTO get_file_list
REMERRORLEVEL 1 = Zeichen(kette) nicht gefunden
REMERRORLEVEL 0 = Zeichen(kette) gefunden
Wenn doch, muessen wir die Eingabe zerlegen.
Code:
REMDoppelpunkt in der Eingabe ?
ECHO "%target%" | FIND ":" > NUL
IF ERRORLEVEL 1 GOTO Backslash
CALL :finde_Trennzeichen :
SET target=%cnt%
SET cnt=
Code:
:Backslash in der Eingabe ?
ECHO "%target%" | FIND "\" > NUL
IF ERRORLEVEL 1 GOTO Wildcards
CALL :finde_Trennzeichen \
SET target=%cnt%
SET cnt=
Wieder findet auch hier der FIND-Befehl Verwendung. Um die Angaben zwischen den gefundenen Trennzeichen zu erhalten, rufen wir eine Subroutine auf und teilen ihr beim Aufruf mit, nach welchem Trennzeichen gefiltert werden soll (CALL :finde_Trennzeichen x).
Code:
:finde_Trennzeichen
FOR /F "TOKENS=1* DELIMS=%1" %%a IN ("%target%") DO (
IF []==[%%b] SET cnt=%%a
SET target=%%b
)
IF []==[%cnt%] GOTO finde_Trennzeichen
GOTO :EOF
REM :end_finde_Trennzeichen
TOKENS gibt an, welche Elemente der aktuellen Kette wir haben moechten. In diesem Fall jeweils das erste Element (1) und alles (*) NACH dem Trennzeichen (zur Rekursion) in je einer eigenen Variablen.
DELIMS (Delimitations = Abgrenzungen) gibt an, bei welchen gefundenen Trennzeichen die Zeichenkette aufgespalten werden soll. Es wird in diesem Fall jeweils beim Aufruf der Subroutine im Parameter %1 uebergeben.
Code:
:Wildcards im Dateinamen ?
SET count=2
ECHO %target% | FIND "*" > NUL
IF ERRORLEVEL 1 SET /A count-=1
ECHO %target% | FIND "?" > NUL
IF ERRORLEVEL 1 SET /A count-=1
IF %count%==0 GOTO get_file_list
SET name=%target%
SET ext=%cnt%
Endlich koennen wir die Dateiliste erstellen. Dazu leiten wir die Ausgabe des DIR-Befehls in eine Datei. Es ist dabei immer guter Stil, dem Anwender so ungefaehr mitzuteilen, was gerade passieren soll, statt ihn vor dem leeren Bildschirm dumm 'rum sitzen zu lassen ...
Code:
:get_file_list
REM Directory Liste erstellen
REMaufsteigend sortiert nach Datum
ECHO.
ECHOpruefe jetzt"%pfad%%name%%ext%". . .
DIR /OD /4 /-P /N "%pfad%%name%%ext%" | FIND /V "<DIR>" | FIND ":" > %TEMP%.\$Dummy1.txt
REM/OD-nach Datum sortieren
REM/-P-wir koennen bei der Ausgabeumleitung keine Stopbefehle gebrauchen; )
REM/N -Ausgabe im "neuen" Format in der Reihenfolge Datum, Zeit, Groesse, Name
REM/4 -die Jahreszahl fuer den Datumsvergleich vierstellig ausgeben. (Wozu, werden wir gleich noch sehen.)
REMFIND /Vzeigt alle Zeilen an, die "Zeichenfolge" NICHT enthalten.
Dann suchen wir die Angaben zu evtl. gefundenen Dateien, in dem wir nach dem Trennzeichen der Zeitangabe suchen (":"). Das ist im Gegensatz zum Datumstrennzeichen naemlich in allen westlichen Win Versionen gleich ... : )
Warum verwenden wir beim DIR-Befehl nicht gleich das Attribut /A-D ?
Ausgabe aller Eintraege, die nicht das Directory-Attribut tragen ?
Weil dann eine Fehlermeldung am Bildschirm ausgegeben wuerde, wenn das angefragte Verzeichnis keine Dateien enthaelt. Das liesse sich aber nicht abfangen und man koennte im Batchablauf nicht darauf reagieren, da die internen Kommandos den ERRORLEVEL nicht setzen/veraendern. Deshalb der kleine "Umweg" ueber den FIND-Filter ; )
Wir haben jetzt also eine Verzeichnisliste namens $Dummy1.txt stilgerecht im Verzeichnis fuer temporaere Dateien (uebernommen aus der Systemvariablen %TEMP%). Existiert kein TEMP-Verzeichnis (unwahrscheinlich) oder wurde die TEMP-Variable geloescht, wird die Liste im aktuell gueltigen Verzeichnis erstellt. Dafuer sorgt der kleine Punkt zwischen %TEMP% und dem Backslash. : D
Diese Liste muessen wir nun noch ein wenig bearbeiten und vor allem auswerten.
Wir benoetigen von den in Frage kommenden Dateien nur das Datum und den Namen ...
Code:
:main
REMListe in Frage kommender Dateien erstellen
ECHO.> %TEMP%.\$Dummy2.txt
SET age=
SET target=
SET count=0
FOR /F "SKIP=3 TOKENS=1,3* DELIMS= " %%a IN (%TEMP%.\$Dummy1.txt) DO (
SET age=%%a
SET target=%%c
CALL :write_file_list
)
IF %count%==0 GOTO no_file
REMsry, aber keine Datei im Pfad gefunden ... :(
Die ersten 3 Zeilen ueberspringen wir dabei (SKIP). Sie enthalten nur Angaben zur Festplatte und zum durchsuchten Pfad.
Ausserdem klaeren wir gleich die Frage: Wurde ueberhaupt eine Datei im abgefragten Verzeichnis gefunden ?
Code:
:write_file_list
ECHO %age% %target%>> %TEMP%.\$Dummy2.txt
SET /A count+=1
GOTO :EOF
REM :end_write_file_list
Zunaechst brauchen wir also das aktuelle Datum. Das steht in der Systemvariablen DATE.
Aber in welchem Format steht es dort? Schliesslich ist bei den Anwendern nicht nur die deutsche Version von Win im Einsatz ...
Code:
REMDas Datum kann in Win in drei verschiedenen Formaten
REMvorliegen, abhaengig von der lokalen Win Version.
REMForm-1 alsTT MM JJJJ - Tag Monat Jahr(z.B. deutsche und UK engl. Vers.)
REMForm-2 alsMM TT JJJJ - Monat Tag Jahr(z.B. US engl. Vers.)
REMForm-3 alsJJJJ MM TT - Jahr Monat Tag(z.B. ungarische Vers.)
REMAls Trennzeichen kommen Punkt, Bindestrich und Schraegstrich in Frage (.-/).
REMSie sind in allen Win Versionen bei der Eingabe als Trennzeichen gueltig.
REM Beim Format dagegen muss bei Ein- und Ausgabe lokal unterschieden werden.
Code:
SET dform=0
FOR %%a IN (jahr monat tag) DO SET %%a=
REMaktuelles Datum
REM !!tag monat jahrsind nur Platzhalter fuer die Werte von %%a %%b und %%c !
REM Sie repraesentieren in diesem Abschnitt nicht zwingend TAG, MONAT und JAHR !!
FOR /F "TOKENS=1,2,3 DELIMS=.-/" %%a IN ("%DATE%") DO (
SET /A tag=%%a
SET /A monat=%%b
SET /A jahr=%%c
)
Code:
IF %monat% GTR 12 SET dform=2
IF %tag% GTR 12 SET dform=1
IF %tag% GTR 31 SET dform=3
IF %dform% GTR 0 GOTO parameter
Ist der linke Teil der Variablen groesser 12 handelt es sich um Form 1 - TT MM JJJJ oder Form 3 - JJJJ MM TT.
Ist dieser Teil also groesser 31, liegt in jedem Fall Form 3 vor.
Was aber, wenn der Tag im Bereich vom 1. bis zum 12. eines Monats liegt und Form 3 ausgeschlossen ist ?
Dann muessen wir die Moeglichkeiten austesten ...
Code:
SET /A tag+=12
ECHO. | DATE %tag%-%monat%-%jahr%
Fangen wir den Check am besten mit dem im deutschsprachigem Raum meistverbreitetem Format an und setzen die Position mit dem vermutetem Tag um den Wert 12 herauf. Denn an dieser Stelle des Programms liegt der Wert fuer den aktuellen Tag des Datums irgendwo zwischen 1 und 12 (jaja, ich weis - genau genommen zwischen 0 und 13).
Indem wir nun 12 hinzu addieren, erhoehen wir den Wert also mindestens auf die Zahl 13 und erreichen damit auf jeden Fall einen Wert groesser als der hoechstzulaessige Wert fuer Monat.
Wie schon erwaehnt, geben interne Kommandos leider keinen ERRORLEVEL zurueck. Deshalb muessen wir den Erfolg oder Misserfolg der Aktion durch nochmaliges Ueberpruefen des Wertes der DATE-Variable feststellen ...
Code:
FOR /F "TOKENS=1 DELIMS=.-/" %%a IN ("%DATE%") DO (
SET /A tag=%%a
)
IF %tag% GTR 12 SET dform=1
Natuerlich stimmt nun das Datum nicht mehr, da wir den Wert fuer den Tag erhoeht hatten. Das muessen wir korrigieren und wieder zuruecksetzen.
Gut, das wir den Wert fuer Tag um genau 12 erhoeht hatten, statt einfach eine beliebige Zahl zwischen 12 und 30/31 (28 ; ) Februar) zu setzen. So koennen wir den richtigen Wert fuer Tag ganz einfach wieder herstellen, indem wir 12 abziehen.
Selbst wenn dies Programm um Mitternacht herum laeuft und mittlerweile gerade ein neuer Tag begonnen hat, ist das Ergebnis dann immer noch das richtige ... : D
Code:
IF %dform%==1 SET /A tag-=12
Ueberpruefen wir das, indem wir die mittlere Position um 12 erhoehen ...
Code:
IF %dform%==0 SET /A monat+=12
ECHO. | DATE %tag%-%monat%-%jahr%
IF %dform%==1 GOTO parameter
nur abfragen muessen wir den (mittleren) Wert nochmal ... (TOKENS=2)
Code:
FOR /F "TOKENS=2 DELIMS=.-/" %%a IN ("%DATE%") DO (
SET /A monat=%%a
)
IF %monat% GTR 12 SET dform=2
IF %dform%==2 SET /A monat-=12
ECHO. | DATE %tag%-%monat%-%jahr%
IF %dform%==2 GOTO parameter
Code:
GOTO date_error
REMSchwerer Fehler! - Kann Systemdatum nicht ermitteln.
REMSollte bei Win Versionen mit westlichem Zeichensatz ausgeschlossen sein.
Code:
:date_error
@ECHO OFF
ECHO.
ECHO--------------------------------------------------------
ECHOSchwerer Fehler! - Kann Systemdatum nicht ermitteln.
ECHO--------------------------------------------------------
ECHO.
ECHO Das Programm wird jetzt geordnet beendet.
ECHO Bitte eine beliebige Taste druecken ...
PAUSE > NUL
GOTO clean-up
Code:
REMLoeschdatum berechnen
FOR %%a IN (jahr monat tag cnt) DO SET %%a=
CALL :datum_%dform%
SET cnt=%days%
CALL :get_loesch_datum
SET datum=%jahr%%monat%%tag%
Code:
REM :akt_Datum
:datum_1
FOR /F "TOKENS=1,2,3 DELIMS=.-/" %%a IN ("%DATE%") DO (
SET /A tag=%%a
SET /A monat=%%b
SET /A jahr=%%c
)
GOTO :EOF
:datum_2
FOR /F "TOKENS=1,2,3 DELIMS=.-/" %%a IN ("%DATE%") DO (
SET /A tag=%%b
SET /A monat=%%a
SET /A jahr=%%c
)
GOTO :EOF
:datum_3
FOR /F "TOKENS=1,2,3 DELIMS=.-/" %%a IN ("%DATE%") DO (
SET /A tag=%%c
SET /A monat=%%b
SET /A jahr=%%a
)
GOTO :EOF
REM :end_akt_Datum
Code:
:get_loesch_datum
IF %tag% LEQ %cnt% (
SET /A tag+=31
FOR %%a IN (5 7 10 12) DO (
IF %monat%==%%a SET /A tag-=1
)
IF %monat%==3 SET /A tag-=3
SET /A monat-=1
)
IF %monat%==0 (
SET /A jahr-=1
SET monat=12
)
IF %tag% GTR %cnt% (
SET /A tag-=%cnt%
SET cnt=0
)
IF NOT %cnt%==0 GOTO :get_loesch_datum
FOR %%a IN (1 2 3 4 5 6 7 8 9) DO (
IF %monat%==%%a SET monat=0%%a
IF %tag%==%%a SET tag=0%%a
)
GOTO :EOF
REMDer 29. Feb. in Schaltjahren findet keine Beruecksichtigung,
REMum die Berechnungen nicht unnoetig komplex zu gestalten.
REM :end_get_loesch_datum
Zunaechst vergleichen wir mal, ob der Wert fuer %tag% kleiner oder gleich dem Wert ist, der der gewuenschten Zahl an Tagen entspricht, die fuer das Mindestalter der zu pruefenden Dateien vorgesehen ist.
Ist das der Fall, addieren wir die Anzahl der Tage der Vormonats zum Wert fuer %tag% hinzu. Vom Wert fuer %monat% ziehen wir natuerlich 1 ab.
Aber die Monate haben unterschiedlich viele Tage ? - Kein Problem. Addieren wir zunaechst auf jeden Fall mal 31. Sieben von zwoelf Monaten des Jahres haben 31 Tage. Mehr als die Haelfte decken wir damit also schon mal ab.

Dann ziehen wir 1 ab, wenn der Vormonat nur 30 Tage hat. Das kommt im Jahr genau nur viermal vor. Aber wieso steht da 5 7 10 und 12 (Mai Juli Oktober Dezember) ? Die haben doch 31 Tage ... Klar - als aktuell gesetzter Monat ... aber ihr jeweiliger Vormonat hat nur 30 Tage ... und DEN loesen wir ja auf ... : )
Dann fragen wir ab, ob der aktuell gesetzte Monat der Maerz ist. Ist das der Fall, ziehen wir fuer den Februar eben 3 Tage von den zuvor zugefuegten 31 ab. Den 29. Februar schenken wir uns und berechnen den Feber immer mit 28 Tagen.
Jetzt (!) ziehen wir vom Wert fuer %monat% 1 ab. Verrechnen wir dadurch zufaellig gerade den Januar und der Wert fuer %monat% faellt auf 0, muessen wir natuerlich vom Wert fuer %jahr% auch 1 abziehen und den Wert fuer %monat% dann auf 12 setzen.
Ist der Wert fuer %tag% jetzt groesser, als der Wert fuer die Anzahl Tage, die eine Datei aelter sein soll, so ziehen wir sie voneinander ab. Wenn nicht, wiederholen wir den Durchgang.
Nun sorgen wir noch dafuer, das die Werte fuer %tag% und %monat% eine fuehrende Null erhalten, falls sie kleiner sind als 10. - Warum ?
Nun - das Ergebnis der Subroutine wird nach dem Ruecksprung zum Wert fuer die Variable %datum% zusammengesetzt in der Form JJJJMMTT.
Das ergibt einen einmaligen und unverwechselbaren Vergleichswert fuer das Loeschdatum.
Ein mathematischer Vergleich in der Form JJJJMMTT kleiner/gleich JJJJMMTT wird IMMER das richtige Ergebnis liefern.
Beim mathematischem Vergleich zweier Zahlen z.B. nach dem Muster TTMMJJJJ kleiner/gleich TTMMJJJJ waere das nicht der Fall ...

Aus diesem Grund liessen wir uns vom DIR-Befehl die Jahreszahl auch vierstellig ausgeben. Damit bei einem Vergleich mit Dateien z.B. von 1999 dann auch das richtige Ergebnis zu Stande kommt ... 19990101 kleiner/gleich 20060201 und nicht etwa 990101 kleiner/gleich 060201 ... na ? gut aufgepasst ? ; )
Nun koennen wir das
Code:
REMDateialter pruefen
ECHO.> %TEMP%.\$Dummy3.txt
FOR %%a IN (jahr monat tag target) DO SET %%a=
GOTO :alter_%dform%
:alter_1
FOR /F "TOKENS=1,2,3* DELIMS=.-/ " %%a IN (%TEMP%.\$Dummy2.txt) DO (
SET tag=%%a
SET monat=%%b
SET jahr=%%c
SET target=%%d
CALL :proof_age
)
GOTO count_hit_lines
:alter_2
FOR /F "TOKENS=1,2,3* DELIMS=.-/ " %%a IN (%TEMP%.\$Dummy2.txt) DO (
SET tag=%%b
SET monat=%%a
SET jahr=%%c
SET target=%%d
CALL :proof_age
)
GOTO count_hit_lines
:alter_3
FOR /F "TOKENS=1,2,3* DELIMS=.-/ " %%a IN (%TEMP%.\$Dummy2.txt) DO (
SET tag=%%c
SET monat=%%b
SET jahr=%%a
SET target=%%d
CALL :proof_age
)
REM GOTO count_hit_lines
Code:
:proof_age
IF %jahr%%monat%%tag% LEQ %datum% ECHO %tag%.%monat%.%jahr% %pfad%%target%>> %TEMP%.\$Dummy3.txt
GOTO :EOF
REM :end_proof_age
Warum nicht einfach nur auf KLEINER vergleichen ? Damit wir auf Wunsch auch Dateien loeschen koennen, wenn sie nur einen Tag alt sind, also das Datum von gestern tragen und der Vergleichswert %datum% ist ja zumindest das Datum vom Vortag ...
Sehen wir nun mal nach, auf wieviele Dateien das Kriterium Loeschalter zutrifft ...
Code:
:count_hit_lines
SET count=1
FOR /F "EOL=- TOKENS=1 DELIMS=[]" %%a IN ('FIND /N " " "%TEMP%.\$Dummy3.txt"') DO (
SET /A count=%%a
)
SET /A count-=1
IF %count%==0 GOTO no_match
REMsry, aber keine Datei im Pfad ist alt genug ... : )
REMFIND /Nzeigt alle "Zeichenfolge" enthaltenden Zeilen mit ihren Zeilennummern an.
REM :end_get_file_list
Und diese Leerzeile wollen wir ja nun nicht unbedingt als Erfolg mit werten ...

Der Parameter /A zum Befehl SET bewirkt, dass das Argument arithmetisch (mathematisch, nummerisch) ausgewertet wird, bevor es der gewuenschten Variablen als Wert zugewiesen wird.
Keine passende Datei gefunden ? Dann geben wir eine entsprechende Meldung aus ...
Code:
:no_match
@ECHO OFF
ECHO.
ECHO--------------------------------------------------------
ECHO Hinweis:
ECHO.
ECHOKeine der Dateien im Pfad ist alt genug.
ECHO--------------------------------------------------------
ECHO.
ECHO Bitte eine beliebige Taste druecken ...
PAUSE > NUL
GOTO done
Code:
REMLoeschliste ist fertig
FOR %%a IN (jahr monat tag cnt) DO SET %%a=
CALL :datum_%dform%
SET cnt=%days%
CALL :get_loesch_datum
ECHO.
ECHO--------------------------------------------------------
ECHO Ihnen wird gleich eine Liste mit%count%Datei(en)
ECHO angezeigt.
ECHO.
ECHO Diese sind vom%tag%.%monat%.%jahr%oder aelter.
ECHO.
ECHO Bitte pruefen Sie die Liste sorgfaeltig und
ECHOentscheiden Sie dann, ob Sie die aufgefuehrte(n)
ECHO Datei(en) wirklich loeschen moechten.
ECHO--------------------------------------------------------
ECHO.
ECHO Zum Fortfahren bitte eine beliebige Taste druecken ...
PAUSE > NUL
TYPE "%TEMP%.\$Dummy3.txt" | MORE
Nun sollten wir dem Anwender aber auch die Chance geben, die Liste nach der Sichtung zu bestaetigen oder ggf. zu verwerfen.
Code:
:Abfrage
SET cnt=
ECHO.
ECHO Abfrage:
SET /P cnt=Diese Datei(en) loeschen ? [J/N]
FOR %%a IN (j J) DO IF [%%a]==[%cnt%] GOTO do_it
FOR %%a IN (n N) DO IF [%%a]==[%cnt%] GOTO end_Abfrage
REM CLS
ECHO.
ECHO Sorry, ungueltige Eingabe:%cnt%
ECHO.
ECHO Zum Fortfahren bitte eine beliebige Taste druecken ...
PAUSE > NUL
GOTO Abfrage
:end_Abfrage
ECHO.
ECHO.
ECHO Ok, diese Dateien werden NICHT geloescht.
ECHO.
GOTO done
Hat der Anwender die Liste bestaetigt, wuenscht das Loeschen der angezeigten Dateien und hat also mit 'j' oder 'J' geantwortet, tun wir den vorletzten Schritt ...
Code:
:do_it
ECHO.
ECHO Dateien werden jetzt geloescht . . .
ECHO @ECHO OFF> %TEMP%.\$Dummy4.txt
ECHO.>> %TEMP%.\$Dummy4.txt
SET target=
SET cnt=
SET count=0
IF NOT %mk%==2 SET cnt=/P
FOR /F "TOKENS=1* DELIMS= " %%a IN (%TEMP%.\$Dummy3.txt) DO (
SET target=%%b
CALL :write_kill_list
)
ECHO.>> %TEMP%.\$Dummy4.txt
ECHO ECHO.>> %TEMP%.\$Dummy4.txt
ECHO ECHO %count%Datei(en) geloescht.>> %TEMP%.\$Dummy4.txt
ECHO ECHO.>> %TEMP%.\$Dummy4.txt
Der Inhalt der Variable %cnt% bestimmt, ob zusaetzlich VOR dem Loeschen JEDER einzelnen Datei eine systemeigene Sicherheitsabfrage erfolgen soll ...
Code:
:write_kill_list
ECHO IF EXIST "%target%" DEL %cnt% "%target%">> %TEMP%.\$Dummy4.txt
SET /A count+=1
GOTO :EOF
REM :end_write_kill_list
Wer dieses Batchprogramm erst einmal voellig gefahrlos testen moechte, ersetzt bitte in der fertigen Batch-Datei zunaechst mal das Befehlswort CALL aus dem naechsten Abschnitt in der Zeile SET cnt=CALL durch das Befehlswort TYPE ...
SET cnt=TYPE
So kann in aller Ruhe betrachtet werden, wie dies Batch-Programm arbeitet ...
Das Erzeugen von zunaechst einer txt-Datei (:write_kill_list), die erst unittelbar vor der Ausfuehrung in eine bat-Datei umbenannt wird (REN), die dann ausgefuehrt, den eigentlichen Loeschjob uebernimmt, ist sozusagen ein letztes doppeltes Sicherheitsnetz.
Bleibt, aus welchen ungluecklichen Umstaenden auch immer (Systemabsturz, Abbruch, Stromausfall, ...) eine txt-Datei im Temp-Verzeichnis zurueck, kann sie normal nicht viel Schaden anrichten. Ein bat-File koennte immer noch von einem DAU oder einfach aus Unkenntnis (Kinder, Ehegatten, Arbeitskollegen, ...) durch fehlerhaftes Anklicken versehentlich gestartet statt betrachtet werden und evtl. nachtraeglich noch unerwuenscht Schaden anrichten.
Code:
ECHO.
SET cnt=CALL
IF EXIST %TEMP%.\$Dummy4.bat DEL %TEMP%.\$Dummy4.bat
IF EXIST %TEMP%.\$Dummy4.txt REN %TEMP%.\$Dummy4.txt $Dummy4.bat
IF EXIST %TEMP%.\$Dummy4.bat %cnt% %TEMP%.\$Dummy4.bat
GOTO done
REM :end_main
... ECHO %count% Datei(en) geloescht. ...
im erzeugten bat-File (siehe Abschnitt :do_it).
Code:
REM :end_job
:done
REM-----------------------
REM :statistics
ECHO.
ECHO Anfrage:%1
ECHO.
ECHOPfad:%pfad%
ECHOName:%name%
ECHO Ext:%ext%
ECHO.
ECHO Datum heute:%date%
ECHOMindestalter: %days%Tag(e)
ECHOVergleichswert:%datum%
ECHODatumsformat:%dform%
ECHO.
ECHO Counter:%count%
ECHOMarker:%mk%
REM-----------------------
SHIFT
IF []==[%1] GOTO clean-up
GOTO parameter
REM :end_done
Der Befehl SHIFT befoerdert dann den aktuell gerade abgearbeiteten Parameter von seinem Platz. Gibt es weitere Angaben, wird der Job mit den naechsten Parametern wiederholt. Dem Batch-Programm koennen also mehrere abzuarbeitende Eingaben beim Aufruf uebergeben werden.
z.B.: older.bat *.bac briefe\*.ba? vorlagen\*.tmp *.tst Kopie*.doc
oder: older.bat "meine Gedichte\*.ba?" "\alte texte\Kopie (?) von *"
Bitte daran denken: Dateiangaben, die Leerzeichen enthalten, muessen in Anfuehrungszeichen gesetzt werden.
Nach dem Job (und natuerlich auch bei jedem vorzeitigem kontrolliertem Beenden des Programms) sollte immer auch aufgeraeumt werden ...
Code:
:clean-up
@ECHO OFF
FOR %%a IN (pfad name ext age jahr monat tag dform datum) DO SET %%a=
FOR %%a IN (count target days cnt mk) DO SET %%a=
FOR %%a IN (1 2 3 4) DO IF EXIST %TEMP%.\$Dummy%%a.txt DEL %TEMP%.\$Dummy%%a.txt
FOR %%a IN (1 2 3 4) DO IF EXIST %TEMP%.\$Dummy%%a.bat DEL %TEMP%.\$Dummy%%a.bat
:ende
ECHO.
Das sollte es jetzt gewesen sein.
Aber ist das Programm schon fertig ?
Naja - Am Anfang eines guten Programms sollte immer stehen, was es macht und unter welchen Bedingungen es das tut. Also ...
Code:
@ECHO OFF
REMolder.bat
REMDateien loeschen, die xx Tage alt und aelter sind
REMworks on win XP & 2K3
REM2006-02-01
REMby Mial
REMverwendete Variable:
REM (pfad name ext age jahr monat tag dform datum)
REM (count target days cnt mk)
REMDATE ERRORLEVEL
CLS
IF []==[%1] GOTO Syntax Fehler
am Anfang einer Batchdatei bewirkt, das waehrend der Abarbeitung nur erwuenschte (sinnvolle) Ausgaben am Bildschirm erscheinen. Fehlt die Zeile oder ist sie auskommentiert (z.B. durch ein REM ), so wird jede abzuarbeitende Zeile der Datei auch am Bildschirm ausgegeben. Das ist aber allenfalls als Hilfsmittel zur Fehlersuche sinnvoll ...
Wir listen die verwendeten Variablen auf und sorgen fuer freien Blick auf die Programmmeldungen. (CLS - clear screen).
Und dann pruefen wir gleich mal, ob beim Programmaufruf ueberhaupt Parameter uebergeben wurden. Ist das nicht der Fall, geben wir eine Kurz-Anleitung zum Programmaufruf mit den moeglichen und wahlfreien Angaben aus ...
Code:
:Syntax Fehler
@ECHO OFF
ECHO.
ECHO--------------------------------------------------------
ECHOSyntax Fehler - der korrekte Aufruf lautet:
ECHO.
ECHOolder[.bat] [/noq] [/d xx] [Lw:\Pfad\][Filename.Ext]
ECHO.
ECHOParameter:/noq-gibt an, das vor
ECHOdem Loeschen der einzelnen Dateien
ECHOKEINE Sicherheitsabfrage erfolgen soll.
ECHO.
ECHOParameter:/d xx oder /D xx
ECHOxxdefiniert die Anzahl Tage, die die zu
ECHOloeschende(n) Datei(en) alt sein soll(en).
ECHO.
ECHOErlaubt sind ganze Zahlen von 1 bis 11680.
ECHO.
ECHOHinweis:
ECHO--------
ECHOPfad- und Dateinamen, die Leerzeichen enthalten,
ECHOmuessen in Anfuehrungszeichen gesetzt werden.
ECHOWildcards (* ?) in Pfad- und / oder Dateinamen
ECHOsind erlaubt und werden ebenfalls verarbeitet.
ECHO.
ECHO !Parameter bitte NICHT in Anfuehrungszeichen.
ECHO--------------------------------------------------------
ECHO.
ECHO Das Programm wird jetzt geordnet beendet.
ECHO Bitte eine beliebige Taste druecken ...
PAUSE > NUL
IF [%mk%]==[] GOTO ende
GOTO clean-up
Code:
:init
SET mk=1
FOR %%a IN (noq NOQ Noq noQ NOq nOQ NoQ nOq) DO (
IF [/%%a]==[%1] SET mk=2
)
IF %mk%==2 SHIFT
IF []==[%1] GOTO Syntax Fehler
Da es sich nur um drei Zeichen fuer eine i.d.R. vermutlich nicht gewuenschte Operation handelt, beruecksichtigen wir auch gleich mal evtl. Tipfehler bei der Eingabe, die in Zusammenhang mit der Shifttaste stehen ... : ( : )
Code:
SET days=2
REMvorgegebenes Mindest-Alter der zu loeschenden Dateien, falls per
REMParameteruebergabe keine abweichende Eingabe vorgenommen wird
und wenn angegeben, lesen wir dann aus, wie alt die zur nachfolgenden Eingabe passenden Dateien mindestens sein sollen, um geloescht zu werden.
Code:
:parameter
IF []==[%2] GOTO split_filename
FOR %%a IN (d D) DO (
IF [/%%a]==[%1] SET days=%2
)
IF %days%==%2 SHIFT
IF %days%==%1 SHIFT
IF []==[%1] GOTO Syntax Fehler
REMevtl. vorhandene fuehrende Nullen entfernen
ECHO %days% | FIND "0" > NUL
IF NOT ERRORLEVEL 1 (
FOR /F "TOKENS=* DELIMS=0" %%a IN ("%days%") DO (
SET days=%%a
)
)
SET /A days=%days%
IF ERRORLEVEL 9000 GOTO out_of_range
IF %days% LEQ 0 GOTO out_of_range
IF %days% GTR 11680 GOTO out_of_range
REMtech. max. abs(32767) - log. max. 365 Tg. x 32 Jahre = 11680 Tg.
Ausserdem findet natuerlich auch eine Gueltigkeitspruefung des uebergebenen Wertes statt.
Code:
:out_of_range
@ECHO OFF
ECHO.
ECHO--------------------------------------------------------
ECHOSyntax Fehler-Out of Range
ECHO.
ECHODer Wert fuer den Parameter ( /d xx oder /D xx )
ECHOliegt ausserhalb des gueltigen Bereichs.
ECHO.
ECHOParameter:/d xx oder /D xx
ECHOxxdefiniert die Anzahl Tage, die die zu
ECHOloeschende(n) Datei(en) alt sein soll(en).
ECHO.
ECHOErlaubt sind GANZE Zahlen von 1 bis 11680.
ECHO--------------------------------------------------------
ECHO.
ECHO Das Programm wird jetzt geordnet beendet.
ECHO Bitte eine beliebige Taste druecken ...
PAUSE > NUL
GOTO clean-up

Bsp.: older.bat M*.txt /d 30 *.bac /d 20 "Kopie (?) von *.doc" d:\texte\*.bak ...
Wird nicht erneut ein Mindestalter angegeben, so wird die jeweils letzte Angabe verwendet.
Wird gar kein Mindestalter angegeben, so wird die Vorgabe aus dem vorstehenden Abschnitt uebernommen.
Damit sollten jetzt alle notwendigen Module erstellt und erlaeutert sein.
Die Anordnung der Module in der fertigen Batch-Datei im Anhang ist fuer die Abarbeitung durch einen Kommando-Interpreter angepasst. Die Reihenfolge entspricht somit nicht dem seriellen Programmablauf sondern dient der Sprungoptimierung. Evtl. zeitrelevante Routinen befinden sich deshalb am Anfang der Datei.
In einer Compiler-Version wuerde z.B. die Modulanordnung wiederum deutlich anders aussehen. Oder die im Batch mehrfach verwendete Befehlszeile
ECHO %target% | FIND "%irgendwas%" > NUL
Sie wuerde dann z.B. in einer Subroutine landen. : D
Dennoch wurde eine gewisse Strukturierung der Uebersichtlichkeit wegen beibehalten. REM Zeilen dienen der Erlaeuterung, Leerzeilen der Gliederung. Die Erfahrung zeigt, das nach einer gewissen Zeit sonst nur erschwert nachvollziehbar ist, was in den Programmzeilen durchgefuehrt wird. ; )
Natuerlich waere eine weitere Optimierung des Codes moeglich. Sie ginge aber vermutlich zu Lasten der Struktur und macht nur fuer wirklich alte Computer evtl. Sinn.
Meine Absicht war es, ein uebersichtliches, zweckgerichtetes, komfortables und moeglichst 'sicheres' Batchprogramm zu schreiben. Gerade auch beim sensiblen Vorgang des Datei loeschens ... Lieber eine Pruefroutine zu viel, als ein ungewollter Datenverlust. Deshalb z.B. auch die staendige Ueberpruefung und die streng definierte Zuweisung an variable Platzhalter.
Jegliche Aenderung, Erweiterung oder 'Optimierung' an diesem Programmcode ist selbstredend erlaubt, geschieht aber natuerlich ausdruecklich auf eigenes Risiko. : )
cu

m,-