bastla
Goto Top

Kommentiertes Batch-Beispiel - Auswertung von Logdateien nach Zeiten

Hallo @all!

Die folgende "Auswertung von Logdateien" soll die Verwendung einiger oft benötigter Batch-Elemente zeigen:
  • "for /f"-Schleifen zum zeilenweisen Auslesen einer Datei bzw der Ausgabe eines Befehles
  • Suche in einer Datei mittels "findstr"
  • Verwendung einer Variablen als Zähler
  • Auslesen des innerhalb einer Schleife veränderten Variableninhaltes (Stichwort "verzögerte Variablenauflösung" bzw "delayedExpansion")
  • Formatierung einer Ausgabe (Zahl rechtsbündig)
  • Ausgabe auf Bildschirm oder in eine Datei
----
Ausgangssituation (siehe dazu den Originalthread unter Suchen von Strings mit von Batch files ):
Zwei Logdateien ("D:\Zeiten.txt", "D:\Gesamtlog.txt") enthalten Zeitangaben bzw zugeordnete Ereignisse.

Aufbau der Datei "Zeiten":
00:00:00
00:01:00
00:02:00
00:03:00
00:04:00
00:05:00
00:06:00
........
23:59:00
Inhalt von "Gesamtlog":
Zu jeder in "Zeiten" enthaltenen Uhrzeit können mehrere Zeilen (mit der Zeit und zusätzlichen Informationen) vorkommen, zB:
00:02:00 Ereigniskennung: 68
00:02:00 Ort: K-1-08
00:02:00 Zusatz: -
00:05:00 Ereigniskennung: 07
00:05:00 Ort: G-3-02
00:05:00 Zusatz: Hier könnten zusätzliche Informationen,
00:05:00 Zusatz: auch auf mehrere Zeilen verteilt,
00:05:00 Zusatz: protokolliert werden - den Text der
00:05:00 Zusatz: übrigen 8 Zeilen spare ich ein ;-)
00:05:00 Zusatz: 
00:05:00 Zusatz: 
00:05:00 Zusatz: 
00:05:00 Zusatz: 
00:05:00 Zusatz: 
00:05:00 Zusatz: 
00:05:00 Zusatz: 
00:05:00 Zusatz: 
usw

Ziel ist eine Auswertung, welche für jede in "Zeiten" erfasste Uhrzeit die Anzahl der zugehörigen Einträge aus der Datei "Ereignisse" ermittelt - das Ergebnis für den oben dargestellten Auszug aus dem "Gesamtlog" wäre etwa:
00:00:00___0
00:01:00___0
00:02:00___3
00:03:00___0
00:04:00___0
00:05:00__14
00:06:00___0
........___0
23:59:00___0

Diese Ausgabe lässt sich mit folgendem Batch erreichen:
@echo off & setlocal enabledelayedexpansion
set "Zeiten=D:\Zeiten.txt"  
set "Log=D:\Gesamtlog.txt"  

for /f "usebackq delims=" %%i in ("%Zeiten%") do (  
    set /a Anzahl=0
    for /f %%a in ('findstr /c:"%%i" "%Log%"') do set /a Anzahl+=1  
    set "Formatiert=____!Anzahl!"  
    echo %%i!Formatiert:~-4!
)

Zur Funktion der einzelnen Zeilen:
@echo off & setlocal enabledelayedexpansion
Während "@echo off" (zum Unterdrücken der Vorweg-Anzeige des auszuführenden Befehles) und "setlocal" (um die Gültigkeit von innerhalb des Batches neu erstellten Variablen auf diesen Batch zu beschränken) eigentlich Standard für den Beginn eines Batches sind, muss hier auch "enabledelayedexpansion" hinzugefügt werden - mehr dazu unten.

set "Zeiten=D:\Zeiten.txt"
set "Log=D:\Gesamtlog.txt"
Auch wenn im weiteren Ablauf die beiden Dateien nur noch je einmal benötigt werden, sorgt die Verwendung von Variablen dennoch für eine übersichtlichere Darstellung und leichtere Wartbarkeit.

for /f "usebackq delims=" %%i in ("%Zeiten%") do (
Schleife, um alle Zeilen der Zeiten-Tabelle einzeln in der Variable %%i zu erhalten.
  • "usebackq" erlaubt es, den Dateinamen/-pfad (in der Variablen %Zeiten%) unter Anführungszeichen angeben zu können - nur für den Fall, dass dieser (etwa nach einer Änderung) Leerzeichen enthielte.
  • "delims=" legt fest, dass es keine Trennzeichen (zur Aufteilung der Zeile in einzelne "tokens") gibt - daher wird %%i jeweils die gesamte Zeile enthalten.
  • Die Verwendung der Klammer ermöglicht, mehrere Batchzeilen für jede Zeile der ausgelesenen Datei "Zeiten" auszuführen und damit den "do"-Teil zu erweitern.

set /a Anzahl=0
Rücksetzen des Zählers für Fundstellen (das "/a" ist eigentlich nicht nötig - zeigt aber sozusagen den "Datentyp" der Variablen - nämlich "numerisch" - an).

for /f %%a in ('findstr /c:"%%i" "%Log%"') do set /a Anzahl+=1
Schleife über alle Zeilen, welche %%i (die vorhin ausgelesene Uhrzeit) enthalten
  • "findstr" mit dem Suchbegriff "Uhrzeit" (%%i) sorgt dafür, dass nur "passende" Zeilen aus der "Gesamtlog"-Datei herausgefiltert und in der Schleife verwendet werden.
  • Auch hier sollen die verwendeten Anführungszeichen um die Variable %Log% herum für Sicherheit sorgen (Leerzeichen im Dateinamen/-pfad).
  • Da die Daten selbst nicht weiter interessieren (sondern nur das Vorhandensein der Zeile), muss bei der "for"-Schleife weder für "tokens" noch für "delims" eine Einstellung vorgenommen werden.
  • Die Schleife wird für jede gefundene Zeile durchlaufen - daher kann mit "set /a Anzahl+=1" (als Kurzfassung für "set /a Anzahl=%Anzahl%+1", oder hier eigentlich "set /a Anzahl=!Anzahl!+1" - warum, steht gleich im nächsten Absatz face-wink) jeweils der "Zählerstand" in der Variablen %Anzahl% (bzw !Anzahl!) erhöht werden. Hier ist "/a" wichtig, damit eine arithmetische Operation vorgenommen wird.

set "Formatiert=____!Anzahl!"
Zwischenspeichern der ermittelten Anzahl und "Auffüllen" von links her mit "_"
  • Damit die Ausgabe übersichtlicher wird, sollen die einzelnen Anzahlen rechtsbündig ausgerichtet untereinander stehen - die Verwendung von "___" erleichtert es, den Zusammenhang zwischen Uhrzeit und Anzahl in der Ausgabe zu erkennen.
  • Damit bereits hier - innerhalb der Schleife - der jeweils aktuellen Wert der Variablen %Anzahl% gelesen (und dann auch ausgeben) werden kann, wird die oben festgelegte "delayedExpansion" benötigt - anderenfalls wäre das Ergebnis immer gleich: ein leerer String. Begründung: Bei nicht "verzögerter" Variablenauflösung würde die Ermittlung des Inhaltes von %Anzahl% nur einmal, und zwar zu Beginn der (ersten) Schleife erfolgen - zu diesem Zeitpunkt gibt es aber die Variable noch gar nicht, und daher würde noch nicht einmal "0" als Ergebnis aufscheinen. "Verzögerung" bedeutet, dass mit der Auswertung der Variablen gewartet wird, bis diese tatsächlich benötigt wird - so kann der aktuelle Wert auch innerhalb der Schleife ermittelt werden. Gekennzeichnet wird die veränderte Behandlung der Variablen durch die Verwendung von "!" anstelle von "%", daher also die Schreibweise !Anzahl!.

echo %%i!Formatiert:~-4!
Ausgabe der Uhrzeit und der (formatierten) Anzahl der Fundstellen
  • Um die Ausgabe der Anzahl und der davor befindlichen "_" immer auf insgesamt 4 Zeichen festzulegen, wird ein Teilstring gebildet: ":~-4" zählt die Zeichen vom Ende "nach vorne" (daher das negative Vorzeichen), sodass also immer die gesamte Zahl (wenn diese nicht mehr als 4 Stellen hat face-wink) und eine passende Anzahl von "_" (3 bei einstelligen Anzahlen, 2 bei zweistelligen, ...) im Teilstring enthalten ist.
  • Auch hier ist wieder "delayedExpansion" erforderlich, da auch die Variable %Formatiert% ihren Inhalt erst in der Schleife erhält und daher nur als !Formatiert! ausgelesen werden kann.

Um auch die letzte Zeile nicht zu unterschlagen: face-wink
)
Damit wird die äußere "for"-Schleife abgeschlossen (ansonsten wäre der "do"-Teil auf den Rest der Zeile 5 beschränkt gewesen).
Anzumerken wäre, dass die vorgestellte Losung ev noch Optimierungspotenzial bietet - so könnte etwa bei Verwendung von "find" mit der Option "/c" (anstelle von "findstr") das Zählen durch den Befehl selbst erfolgen - allerdings ginge dies zulasten der Flexibilität, da die Ausgabe von "find" (am Beispiel der Zeit "00:05:00") folgendes Format hat:
---------- D:\GESAMTLOG.TXT: 14
Weder die Leerzeichen, noch uU der ":" (der Pfad könnte - theoretisch - auch relativ bzw ohne Laufwerksangabe festgelegt sein) bieten ein sicheres Trennzeichen für eine Zerlegung - leicht zu erkennen, wenn Pfad und Dateiname zB auf "..\Aktuelle Logs\Gesamtlog Ereignisse.txt" geändert würden und daher das "find"-Ergebnis so aussähe:
---------- ..\AKTUELLE LOGS\GESAMTLOG EREIGNISSE.TXT: 14
In diesem Fall müsste zusätzlich auch der Batchcode angepasst werden. [Edit] Mit der von LotPings unten dargestellten Schreibweise (per Eingabumleitung) wird auch "find /c" uneingeschränkt verwendbar. [/Edit]

Um trotzdem auch diese Variante darzustellen: Die Zeile 6 könnte entfallen, und die Zeile 7 (die neue Zeile 6) wäre:
for /f "tokens=3" %%a in ('find /c "%%i" "%Log%"') do set "Anzahl=%%a"
Voraussetzung ist hier, dass der Pfad/Dateiname keine Leerzeichen enthält, da ansonsten die Anzahl nicht im (anhand des Default-Trennzeichens "Leerzeichen" zerlegten) Teil ("token") 3 der Zeile zu finden wäre - für das zweite Beispiel oben wäre etwa "tokens=5" erforderlich.
Die Ausgabe des Batches erfolgt auf den Bildschirm - um das Ergebnis in Dateiform zu erhalten, könnte entweder der Batch (Annahme: gespeichert im aktuellen Verzeichnis unter "Auswertung.cmd") mit Ausgabeumleitung gestartet
Auswertung.cmd>D:\Anzahlen.txt
oder die Umleitung in der letzten Zeile ergänzt werden:
)>>"D:\Anzahlen.txt"
  • Im ersten Fall wird die "Gesamtausgabe" des Batches in eine Datei umgeleitet - dadurch kann mit der Schreibweise ">" automatisch eine bereits vorhandene Ausgabedatei durch die neue Ausgabe überschrieben werden.
  • Bei der Umleitung innerhalb des Batches muss hingegen mit ">>" ein Hinzufügen der einzelnen Zeilen zum jeweiligen "Zwischenergebnis" erfolgen (">" würde dazu führen, dass nur die letzte Zeile in der Datei stünde) - dies macht das Löschen einer ev schon bestehenden Ergebnisdatei am Beginn des Batches erforderlich.
  • Natürlich sollte auch der Dateipfad der Ausgabedatei besser in eine Variable geschrieben werden.

Eine vorläufig endgültige face-wink Fassung sähe dann so aus:
@echo off & setlocal enabledelayedexpansion
set "Zeiten=D:\Zeiten.txt"  
set "Log=D:\Gesamtlog.txt"  
set "Erg=D:\Anzahlen.txt"  

if exist "%Erg%" del "%Erg%"  

for /f "usebackq delims=" %%i in ("%Zeiten%") do (  
    set /a Anzahl=0
    for /f %%a in ('findstr /c:"%%i" "%Log%"') do set /a Anzahl+=1  
    set "Formatiert=____!Anzahl!"  
    echo %%i!Formatiert:~-4!
)>>"%Erg%"  

Es würde mich freuen, wenn dieses Beispiel hilfreich war - für Fragen, Anregungen oder Beschwerden habe ich aber gleich unterhalb jede Menge Platz gelassen ... face-wink

Grüße
bastla

Content-ID: 94181

Url: https://administrator.de/contentid/94181

Ausgedruckt am: 24.11.2024 um 16:11 Uhr

AndreasA
AndreasA 15.08.2008 um 12:28:42 Uhr
Goto Top
back-to-topHallo bastla

Ein sehr nützliches Tutorial, auch wenn man die meisten beschriebenen Dinge als "Batch"-Programmierer wissen sollte face-wink. Du hast alles auf den Punkt gebracht was wichtig ist ohne zuweit ausholen zu müssen. Perfekter kann man ein Tutorial nicht schreiben.

Wie auch all deine Kommentare sehr vorbildlich.... weiter so

Grüße vom AndreasA aus Bärlin face-smile
sysad
sysad 19.08.2008 um 17:29:11 Uhr
Goto Top
Sehr gute Idee. Und speziell beim 'Batchen' kann man immer was von anderen lernen, weil die Befehle kurz und knackig sind und nicht wie bei einem C-Programm eben mal 10000 Zeilen 'überflogen' werden müssen.

Danke! Das musste ja auch mal gesagt werden.....
77559
77559 15.04.2009 um 20:57:44 Uhr
Goto Top
Hallo bastla,
Ich finde dein Tutorial auch gut.
Kleiner Verschlimmbesserungsvorschlag:

Der Find Befehl hat die /C Option zum nur Zählen, ohne die einzelnen Fundstellen auszugeben,
damit dürfte der Batch bei großen Logdateien schneller sein. Das Rücksetzen von Anzahl entfällt dann auch.
:: LogZeiten
@echo off & setlocal enabledelayedexpansion 
set "Zeiten=.\Zeiten.txt"   
set "Log=.\Gesamt.log"   
set "Erg=.\Anzahlen.txt"  
Type NUL >"%Erg%"  
for /f "usebackq delims=" %%i in ("%Zeiten%"  
  ) do for /f %%a in ('find /c "%%i" ^<"%Log%"'  
    ) do if %%a GEQ 1 set Anz=____%%a& echo %%i!Anz:~-4!>>"%Erg%"  

Technisch gesehen ist der For Befehl mit den beiden nächsten eingerückten Zeilen ein einziger Befehl.
Das ^ als letztes Zeichen Escaped das Return und der For Befehl kann hinter der öffnenden bzw. vor
der schließenden Klammer einfach in der nächsten Zeile fortgesetzt werden da der Cmd-Interpreter
weitere Information voraussetzt. Diese Schreibweise erleichtert das posten speziel in newsgroups mit
max ~72 Zeichen/Zeile

Type NUL >"%Erg%"
Setzt die Dateilänge auf Null, unabhängig von der vorherigen Existenz der Datei.

Zum Erzeugen der Zeilen-Datei im Minuten-Abstand:
:: GenZeiten.cmd
@echo off&setlocal EnableDelayedExpansion
set "Zeiten=.\Zeiten.txt"   
if Not exist "%Zeiten%"^  
  for /L %%h in (100 1 123
    ) do for /L %%m in (100 1 159
       ) do set _=%%h%%m&echo !_:~1,2!:!_:~4,2!:00>>%Zeiten%

Gruß
bastla
bastla 15.04.2009 um 21:29:52 Uhr
Goto Top
Hallo LotPings!

Danke für die Hinweise face-smile, insbesondere hinsichtlich "find /c" - ist hier zweifellos die beste Wahl (die oben in dieser Hinsicht gemachte Einschränkung gilt bei Verwendung der Eingabeumleitung - oder alternativ eines vorhergehenden type "%Log%"|, etwa für die implizite Konvertierung von Unicode-Files - dann nicht mehr) ...
Um die ursprünglich gewünschte Auswertung zu erhalten, wäre noch eine geringfügige Korrektur erforderlich:
@echo off & setlocal enabledelayedexpansion 
set "Zeiten=.\Zeiten.txt"   
set "Log=.\Gesamt.log"   
set "Erg=.\Anzahlen.txt"  
Type NUL >"%Erg%"  
for /f "usebackq delims=" %%i in ("%Zeiten%") do (  
    for /f %%a in ('find /c "%%i" ^<"%Log%"') do (  
        set Anz=____%%a& echo %%i!Anz:~-4!>>"%Erg%"  
    )
)
Die Aufteilung der Zeilen ist sicherlich zu einem gewissen Teil auch Geschmackssache - ich finde meine bisher verwendete Schreibweise (hier nochmals für Deine Version dargestellt) übersichtlich genug und werde dabei bleiben.
Zu
Type NUL >"%Erg%"
Macht hier keinen Unterschied (da auf jeden Fall eine Ergebnisdatei entstehen wird), erzeugt aber ansonsten immer eine Datei (auch wenn nicht benötigt, da gar keine Ausgabe erfolgt), was gelegentlich (zB nachfolgendes if exist) auch kontraproduktiv sein kann ...
Das Erzeugen der Minuten-Datei war zwar nicht erforderlich, da lt ursprünglicher Problemstellung bereits vorgegeben, ist aber ein schönes Beispiel für geschachtelte Schleifen. face-smile

Grüße
bastla
77559
77559 15.04.2009 um 21:44:16 Uhr
Goto Top
Zitat von @bastla:
Die Aufteilung der Zeilen ist sicherlich zu einem gewissen Teil auch Geschmackssache -
ich finde meine bisher verwendete Schreibweise übersichtlich genug und werde dabei bleiben.

Ist mir klar, deswegen schrub ich ja auch Verschlimmbesserung face-wink

Gruß
LotPings
bastla
bastla 15.04.2009 um 22:00:10 Uhr
Goto Top
Ist mir klar, deswegen schrub ich ja auch Verschlimmbesserung face-wink
... und ich frug mich schon, worauf Du Dich dabei eigentlich bezogen hattest ... face-wink

Grüße
bastla