Batch-Programmierung: Programmierungshilfen


Ändern des Editors zum Bearbeiten von Batchdateien

Bearbeiten

Wenn man im Windows Explorer mit der Rechten-Maus-Taste (RMT) auf eine *.bat klickt, so werden einem die Befehle Öffnen und Bearbeiten angeboten.
Öffnen: führt die Batchdatei aus. Mit dem Befehl
Bearbeiten: wird die Batchdatei in den Texteditor notepad.exe zum Bearbeiten geöffnet.
Auch wenn Notpad zum Bearbeiten von Batchdateien ausreicht, so möchte man häufig doch die Batchdateien mit einem anderen, komfortableren Editor bearbeiten, der z. B. Syntaxhervorhebung (Syntaxhighlighting) beherrscht.

Um einen anderen Editor (z. B. Syn) zu verwenden, muss man in der Registry an der Stelle:

HKEY_CLASSES_ROOT\batfile\shell\edit\command 

den Standard Wert

(Standard) = %SystemRoot%\System32\NOTEPAD.EXE %1

auf den Startbefehl des entsprechenden Editors ändern. Z. B.

HKEY_CLASSES_ROOT\batfile\shell\edit\command | (Standard) = c:\Programme\Editor\syn\syn.exe %1

Datum und Uhrzeit anzeigen

Bearbeiten

( der Inhalt des Abschnittes ist stark von der Windows-Lokalisierungseinstellungen abhänging und gilt für die Einstellung "Deutschland" )

Für Log-Dateien ist es wichtig, dass man die Logeinträge mit Datum und Uhrzeit versehen kann:

echo %date:~0% - %time:~0,8% Uhr

Ergebnis: 11.09.2010 - 15:59:53 Uhr

Hierbei steht ":~0,8" für die Angabe der Stellen. Die Syntax ist:[1]

%variable:~skip[,keep]%

Mit "0,8" wird angegeben, dass die Ausgabe der Zeit bei Position 0 beginnen soll und insgesamt 8 Stellen beinhalten soll, denn der erste Wert (0) gibt an, wieviel übersprungen werden soll (skip), der zweite Wert (8), wieviele Stellen behalten werden sollen (keep). Es sollen demnach 0 Stellen übersprungen und 8 Stellen behalten werden und so ist das Ergebnis: 15:59:53 Die maximale Stellenanzahl ist hier 11 (0,11), da die %time% Variable am Ende noch ein Komma und Zehntel- und Hun­derts­tel­se­kun­de beinhaltet.

Eine weitere Anwendung findet so ein Zeitstempel bei der Erstellung der eindeutigen Datei-/Ordnernamen:

set d=%date%
mkdir %USERNAME%-%d:~0,2%%d:~3,2%%d:~6,2%

Wobei man beachten muss, dass

  1. die Pseudo-Umgebungsvariablen %date% und %time% von Windows jeweils beim Aufführen eines Kodeabschnitts (einer Zeile oder einer for-Schleife) genau einmal ausgewertet und ersetzt werden. Deswegen muss man sie ggf. einmal abfragen, in der Zwischenvariablen speichern und danach nur die Zwischenvariablen verwenden ( die Konstrukte wie %time:~0,2%%time:~3,2%%time:~6,2% vermeiden - zumindest, wenn sie sich auf mehrere Zeilen verteilen), weil die mehrmalige Benutzung von der %date% und %time% in seltenen unglücklichen Fällen unterschiedliche Werte liefern kann, was zu sehr schwer auffindbaren sporadisch auftretenden Fehlern führen könnte. Siehe dazu auch über "EnableDelayedExpansion" unten ;
  2. %time% vor 10 Uhr eine Zeit ohne führende Null liefert. Stattdessen erhält man ein Leerzeichen, was bei einem sortiergerechten Zeitstempel zu Problemen führen kann. So zum Beispiel werden die Datei-/Ordnernamen mit dem Zeitstempel einen Leerschritt beinhalten, was eine entsprechende Benutzung von Anführungszeichen anfordert. Die If-Bedingung prüft in der Variablen SORTTIME, ob das erste Zeichen ein Leerzeichen ist. Ist das vor 10 Uhr morgens der Fall, wird das Leerzeichen durch eine 0 ersetzt:
set t=%time%
set SORTTIME=%t:~0,2%%t:~3,2%%t:~6,2%
if "%SORTTIME:~0,1%"==" " set SORTTIME=0%SORTTIME:~1,6%

Beim Datum ist diese Angabe hier nicht nötig, da dieses standardmäßig im tt.mm.jjjj-Format ausgegeben wird. Wer aber nur das Jahr haben will, kann "%date:~-4%" eingeben und erhält damit die letzten 4 Zeichen. Für ein sortiergerechtes Datum in der Umgebungsvariablen sortdate sorgt z. B.

set d=%date%
set SORTDATE=%d:~-4%-%d:~3,2%-%d:~0,2%
echo %SORTDATE%

Wert der Umgebungsvariablen: 2009-04-20

Beachte: In einer Batchdatei kann die Verwendung von %DATE% und insbesondere von %TIME% dazu führen, dass sich die ausgegebene Uhrzeit nicht aktualisiert. Hierzu folgendes Beispiel:

@echo off
echo ## Die aktuelle Zeit ist: %TIME%
echo ## bitte 5 Sec. warten ...
ping localhost -n 5 > NUL
echo ## jetzt sind genau 5 Sec. vergangen ,TIME liefert %TIME%, das ist noch OK
echo ## doch in der FOR Schleife wird bereits die alte Zeit verwendet.
for /L %%N IN (0, 1, 3) DO (
    echo %time%
    pause
    )
echo ## und dies bleibt für jede Ausgabe innerhalb der FOR-Schleife so.
echo.
echo ## Auch in z.B. IF-Anweisungen ist das so.
if TRUE==TRUE (
        echo 1. Zeit in der If Anweisung: %TIME%
        echo Warte  5 Sec.
        ping localhost -n 5 > NUL
        echo 2. Zeit in der If Anweisung: %TIME%
        echo Warte nochmals  5 Sec.
        ping localhost -n 5 > NUL
        echo 3. Zeit in der If Anweisung: %TIME%
        )
echo ## Dabei ist es bereits: %TIME%
pause

Damit %DATE% und %TIME% die richtigen Werte ausgeben, muss unbedingt die verzögerte Erweiterung von Umgebungsvariablen mit dem SetLocal Befehl[2]

SetLocal EnableDelayedExpansion

aktiviert werden.

Hier das korrekte Beispiel:

@echo off
SetLocal EnableDelayedExpansion
echo ## Die aktuelle Zeit ist: %time:~0,8%
echo ## bitte 5 Sec. warten ...
ping localhost -n 5 > NUL
echo ## jetzt sind 5 Sec. vergangen, TIME liefert %TIME%, das ist OK
echo ## Jetzt gibt auch die FOR Schleife die korrekte Zeit aus.
for /L %%N IN (0, 1, 3) DO (
    echo !TIME!
    pause
    )
echo.
echo ## Auch in z.B. IF-Anweisungen ist es jetzt richtig.
if TRUE==TRUE (
        echo 1. Zeit in der If Anweisung: !TIME!
        echo Warte 5 Sec.
        ping localhost -n 5 > NUL
        echo 2. Zeit in der If Anweisung: !TIME!
        echo Warte nochmals  5 Sec.
        ping localhost -n 5 > NUL
        echo 3. Zeit in der If Anweisung: !TIME!
        )
echo ## Es ist jetzt: %TIME%
EndLocal
pause

Lokalisierungsunabhänginge Datumsermittlung

Bearbeiten

Wenn man Skripte häufig auf unterschiedlichen Umgebungen laufen lassen will, ist eine sprachunabhänige Zeit- und Datumsermittlung erforderlich.

Dies kann wie folgt erreicht werden:

for /f %%x in ('wmic path win32_localtime get /format:list ^| findstr "="') do set %%x
REM Fuehrende Nullen ergaenzen falls der Wert einstellig ist
if "%Month:~1,1%"==""   set Month=0%Month%
if "%Day:~1,1%"==""     set Day=0%Day%
if "%Hour:~1,1%"==""    set Hour=0%Hour%
if "%Minute:~1,1%"==""  set Minute=0%Minute%
if "%Second:~1,1%"==""  set Second=0%Second%
set MYDATE=%Year%-%Month%-%Day%
set MYTIME=%Hour%.%Minute%H

Ausgaben besser anzeigen

Bearbeiten

Wenn man nicht die Ausgabe von Befehlen per @echo off "Ausblendet" kann man am besten das Prompt ändern, sodass man besser erkennen kann, was passiert:

@prompt -$G

Der Prompt ist dann ->


Unterroutinen und Unterprogramme

Bearbeiten

Unterroutinen kann man mittels goto oder call und Unterprogramme mit Hilfe von call realisieren.

call:unterroutine Hallo
echo Fertig!
goto:eof

:unterroutine
   echo Übergebener Parameter an Unterroutine: %1
goto:eof

Beachten Sie, dass Sie beim Aufruf von Unterroutinen per call Probleme mit Filehandles bekommen können. Dies liegt darin begründet, dass ein Aufruf per call als Aufruf eines Unterprogramms interpretiert wird, während es sich bei goto stets um Unterroutines handelt.

Anmerkung: goto:eof ist eine Spezialmarke mit der Sie stets zum Ende Ihres Skriptes (bzw. In Unterroutinen zurück zum Aufruf) springen.

Benutzereingaben mittels "set /P"

Bearbeiten
@echo off
    set /P w= [i]nstallieren / [d]eInstallieren?
    REM die option /I beim if bewirkt, dass nicht
    REM zwischen Gross und Kleinschreibung
    REM unterschieden wird.
    if /I "%w%"=="i" goto Install
    if /I "%w%"=="d" goto Deinstall
    echo Fehler: [%w%]
goto ende

:Install
    echo "installieren" ausgewählt
goto ende

:Deinstall
    echo "deinstallieren" ausgewählt
goto ende


:ende
    echo.
    pause

stdout in Umgebungsvariable speichern

Bearbeiten

Falls man den stdout in einer Umgebungsvariablen speichern möchte, muss man das komplizierter umsetzen. Es gibt zwei verschiedene Möglichkeiten dies anzugehen. befehl | set /P variable= funktioniert nämlich nicht. Stattdessen braucht man:

BEFEHL > temp.txt
set /p BefehlOutput= < temp.txt
del temp.txt

Oder:

FOR /F %%i IN ('BEFEHL') DO set BefehlOutput=%%i

Oder mit "usebackq"-Option:

FOR /F "usebackq" %%i IN (`BEFEHL`) DO set BefehlOutput=%%i

Die Zeichenkette zwischen den einfachen Anführungszeichen wird dabei als Befehlszeile betrachtet und von einer untergeordneten CMD.EXE ausgeführt. %BefehlOutput% kann nun beliebig gebraucht werden.

Beispiel:

Bearbeiten

Code:

@echo off
FOR /F %%i IN ('CD') DO set verzeichnis=%%i
echo %verzeichnis%

Ausgabe:

C:\Programme\Batch

Vorsicht ist geboten bei Befehlen, welche mehrzeilige Ausgaben produzieren und bei solchen, welche in ihrer Ausgabe auch Leerzeichen enthalten können. Da das Standardtrennzeichen ein Blank ist, muss man, wenn man nicht will, dass die Variable nur bis zum Blank gefüllt wird, das Standardtrennzeichen verändern. FOR /F "delims=" %%i IN ('CD') DO set verzeichnis=%%i entfernt jede Art von Trennzeichen. Bei Befehlen, welche mehrzeilige Ausgaben zur Folge haben, bleibt jeweils die letzte Zeile in der Variablen erhalten.

Dateien und Verzeichnisse auflisten

Bearbeiten

Hier ist ein Beispiel, in dem alle Dateien, auf welche die Filterbedingung zutrifft, aufgelistet werden. Außerdem werden die Dateianzahl und die Dateigrößen addiert.

@echo off
set Filter=*.*
set /A DateiAnzahl=0
set bytes=0

for /R %pfad% %%f in (%Filter%) do (
    set /A DateiAnzahl += 1
    echo %%f - %%~zfBytes
    set /A bytes=bytes+%%~zf
)

echo.
echo %~dp0%Filter%
echo Es sind %DateiAnzahl% Dateien vorhanden.
echo Alle Dateien zusammen: %bytes%Bytes
set /A kbytes=bytes/1024
echo umgerechnet sind das %kbytes% KBytes
echo.
pause

Oft ist es hilfreich, dass nach dem Beenden des Batch-Programms das Eingabeaufforderungsfenster offen bleibt. So kann man Ausgaben nachlesen oder evtl. aufgetretene Fehler entdecken. Nun könnte man einfach am Ende eine pause einfügen. Dabei kann der User einfach das Fenster schließen oder ENTER drücken. Man kann aber auch einfach eine zeitliche Pause mit ping realisieren:

@echo off
echo Ich schließe gleich.
@ping localhost -n 2 >NUL

Dabei kann man die Zeit mit dem Parameter -n variieren.

Bei installiertem Windows Server 2003 Resource Kit Tools steht der Befehl "sleep" zur Verfügung, welcher dieselbe Funktionalität (zeitliche Pause) bietet.

Seit Windows XP[3] kann man auch den Befehl timeout nutzen:

@echo off
echo Jetzt kommt eine Pause von 20 Sekunden, zum vorzeitigen Ende eine Taste drücken
timeout /t 20
echo Jetzt musst du 10s warten, Tastendrücke werden ignoriert
timeout /t 10 /nobreak
echo Und jetzt geht es erst weiter, wenn du eine Taste drückst
timeout /t -1

Die Anzahl an Sekunden wird mit dem Parameter /t übergeben, wird ein negativer Wert angegeben wird unendlich gewartet. Der Befehl beendet sich, wenn man eine Taste drückt, es sei denn man startet mit dem Parameter /nobreak

Minimiert ausführen

Bearbeiten

Hin und wieder ist es sinnvoll, dass die Batchdatei minimiert ausgeführt wird (z. B. eine Login-Batch-Datei). Es ist möglich, dass man die Batchdatei normal startet und sie sich selber minimiert ausführt. Der Nachteil ist allerdings, dass sich kurzzeitig ein Eingabeaufforderungs-Fenster öffnet.

@echo off
if not "%1"=="" goto %1

start /MIN cmd.exe /C "%~nx0 begin"
goto:eof

:begin
    echo Hallo, ich laufe minimiert!
    pause
goto:eof

Noch eine Konstruktion ganz ohne Labels, nach diesem Newsgroup-Beitrag

@set !=||(set !=1&start "%~dpnx0" /min cmd /c %0 %*&set !=&goto :eof)

Anmerkung: Sollte die Command-Processor-Option "DelayedExpansion" in der Registry aktiviert sein (siehe unter Hilfe "cmd /?"), lässt sich ein "!" als Variablenname nicht verwenden. In diesem Fall -bzw. sinnvollerweise immer- den Variablennamen ändern auf x oder y oder # oder @....

Beispiel:

@set #=||(set #=1&start "%~dpnx0" /min cmd /c %0 %*&set #=&goto :eof)

Noch eine Variante der oben gezeigten Beispiele, die bei mir funktioniert, da ich mit der Konstruktion 'ohne Labels' auf einem x64 System Probleme hatte:

if "%1"=="" start /min cmd.exe /C "%~dpnx0 x"&goto :eof

Mittels start /LOW die Priorität festlegen

Bearbeiten

Manchmal ist es hilfreich, wenn die Batchdatei mit einer niedrigen Priorität läuft. Das kann man mittels start /LOW erreichen. Weitere Optionen sind NORMAL, HIGH, REALTIME, ABOVENORMAL und BELOWNORMAL. Das Beispiel zeigt, wie eine Batchdatei quasi sich selber in die niedrige Priorität versetzten kann. In dem Fall klappt es allerdings nur, wenn beim ersten Start kein Parameter übergeben wurde.

@echo off
if "%1"=="" (
    start /WAIT /LOW /B cmd.exe /V /C "%~0" weiter_machen
    goto:eof
)
echo Jetzt laufe ich mit niedriger Priorität!
echo Überprüfe es im Taskmanager!
pause

Funktionsweise: Das Prinzip ist eigentlich ganz einfach. Wenn kein Parameter übergeben wird, wird angenommen, daß die Batchdatei zum ersten mal gestartet wurde. Die if "%1"=="" Bedingung ist also erfüllt. Mittels start wird dann dieselbe Batchdatei mit veränderter Priorität gestartet, allerdings mit einem angehängten Parameter weiter_machen (Könnte auch irgendwas anderes sein!). Somit ist beim nächsten Aufruf die if "%1"=="" Bedingung nicht mehr erfüllt und der normale Teil der Batchdatei wird abgearbeitet.

Wenn man der Batchdatei einen Parameter übergeben möchte (z. B. ein Dateiname o.ä.) muss man alle Parameter verschieben:

@echo off
if "%2"=="" (
    start /WAIT /LOW /B cmd.exe /V /C "%~0" %1 weiter_machen
    goto:eof
)
echo Jetzt laufe ich mit niedriger Priorität!
echo Nun kann [%1] 'bearbeitet' werden...
pause

Probleme mit Variablen

Bearbeiten

Wenn man sich die Hilfeseiten zu set ,mittels set /? durchliest (weitere Informationen unter setlocal /? und cmd /?), stößt man auf das Thema verzögerte Erweiterung von Variablen. Das will ich hier mal anhand von Beispielen erklären:

Das Problem

Bearbeiten
set test=1
if "%test%"=="1" (
    set test=2
    echo Wert von 'test' im IF-Block: %test%
)
echo Wert von 'test' nach IF-Block: %test%

Man sollte meinen, dass der Wert von %test% in beiden Ausgaben 2 ist. Doch leider ist es nicht so. Denn innerhalb des IF-Blocks wird das Neusetzen der Variable test von 1 auf 2 noch nicht aktiv und somit ist das Ergebnis Wert von 'test' im IF-Block: 1 Erst nach dem IF-Block ist der Wert aktualisiert: Wert von 'test' nach IF-Block: 2

Lösung: cmd.exe /V:ON

Bearbeiten

In einer Batchdatei, die mit cmd /V:ON gestartet wurde, werden Variablen innerhalb von Befehlsblöcken aktualisiert. Jedoch kann man sie nicht gewohnt mit %test% ansprechen, sondern mit !test!. Der Nachfolgende Code startet automatisch die Batch Datei neu mit eingeschalteter verzögerte Erweiterung von Variablen und übergibt sämtliche Eingabeparameter an sich selbst.

@echo off
set delayedExpansion=off
if "true"=="true" (
	set delayedExpansion=on
	if "!delayedExpansion!" NEQ "on" (
		cmd.exe /V:ON /C "%~f0 %*"
		goto:eof
	) else (
		REM Der Code kann hier
	)
)
REM sowie hier eingefügt werden.
REM Start des Beispiels
set test=1
if "%test%"=="1" (
    set test=2
    echo Wert von 'test'-Prozent in dem IF-Block: %test%
    echo Wert von 'test'-Ausrufezeichen in dem IF-Block: !test!
)
echo Wert von 'test' nach dem IF-Block: %test%
pause
REM Ende des Beispiels
@echo on
REM Wegen der doppelten Ausführung muss unbedingt ein 'exit' am Ende stehen.
exit

Lösung: setlocal EnableDelayedExpansion

Bearbeiten

Mit setlocal EnableDelayedExpansion wird die verzögerte Erweiterung von Variablen nur bis zum dazugehörigen endlocal oder dem Ende der Batch Datei aktiviert.

@echo off
setlocal EnableDelayedExpansion
REM Start des Beispiels
set test=1
if "%test%"=="1" (
    set test=2
    echo Wert von 'test'-Prozent in dem IF-Block: %test%
    echo Wert von 'test'-Ausrufezeichen in dem IF-Block: !test!
)
echo Wert von 'test' nach dem IF-Block: %test%
pause
REM Ende des Beispiels
endlocal
@echo on
exit

Ausgaben/Fehler unterdrücken

Bearbeiten

Manchmal möchte man per Batch ein Programm starten aber es soll dabei keine Ausgabe gemacht werden. Das ist recht einfach:

MeinProgramm.exe >NUL

Es könnte aber sein, dass evtl. Fehler dennoch ausgegeben werden. Das liegt daran, daß die Programme in dem Fall auf stderr statt stdout schreiben. Um auch in dem Fall die Ausgabe zu unterdrücken, kann man mit einem zusätzlichen 2>&1 die Ausgaben von stderr auf stdout umleiten. Da stdout dann nach NUL verschoben wird, sieht man absolut nichts:

MeinProgramm.exe >NUL 2>&1

Professionelle Message-Fenster erzeugen

Bearbeiten

Bisher konnte man in Batch keine Fenster erzeugen, bzw. nur in Windows XP mithilfe des Windows Nachrichtendienstes. Doch der ist für Anwendungen viel zu unpraktisch, da man nicht einmal den Fenstertitel bestimmen kann und außerdem ist dieser unter Vista oder Windows 2000 nicht verfügbar.

Ich habe jedoch ein kleines Schlupfloch gefunden, mit dem man dennoch Fenster erzeugen kann:

@echo off
Echo msgbox "Text",0,"Fenstername" >Test.vbs 
ping localhost -n 3 >NUL
start Test.vbs
pause

Erklärung: Man erzeugt hier mithilfe des "Größer als" Symbols (Eng.: greater than) > einen Temporären VBScript, der in der Lage ist, ein Messagefenster zu erzeugen. Dies sorgt in Anwendungen für mehr Übersicht und Professionalität.

Falls das nicht funktioniert

Bearbeiten

Falls statt eures Textes die Meldung :

"Der Zugriff auf den Windows Scripthost ist auf diesem Computer deaktiviert"... erscheint, dann ist das Öffnen von VBscripts und JScripts aus Sicherheitsgründen verboten.

Um das zu ändern, öffnet ihr den Windows Registrierungseditor (regedit.exe) und löscht NUR folgenden Wert :

HKey_Local_Machine\Software\Microsoft\WindowsScriptHost\Enabled

dann dürfte es funktionieren.


Sicherheitshinweise:

  • Erstellt vor der Änderung eine Sicherheitskopie eurer Werte!
  • Die "Sicherheitsgründe" verringern unter anderem die Angriffsfläche für Würmer u.ä. Schädlinge, dies sollte bei einer Änderung der Sicherheitsrichtlinien per Registrierungseditor nicht vollständig vernachlässigt werden.

Status über bearbeitete Zeilen ausgeben

Bearbeiten

Häufig bearbeitet man eine Liste von Objekten mit einer FOR-Schleife. Damit man darüber informiert ist wie weit die Bearbeitung bereits fortgeschritten ist, kann man die Anzahl der Bearbeiteten Objekte im titel der DOS-Box ausgeben lassen.

Schritt 1 - Ermitteln / zählen der zu bearbeitenden Zeilen: dies geht am besten mit folgender FOR-Schleife:

FOR /F  "eol=# tokens=1,2,3 " %A IN ('find /c ";"liste.txt') DO echo %C Zeilen

Wobei das ";" ein Zeichen sein muss das in jeder zu verarbeitenden Zeile vorkommt. Dies ist am schnellsten. Will man wirklich jede Zeile zählen unabhängig vom Inhalt, dann kann man auch folgenden Befehl verwenden:

FOR /F "delims=:" %%A IN ('findstr /N .* "liste.txt"') DO echo %%A Zeilen

Jetzt bedarf es noch des SET-Befehl zum Berechnen der aktuellen Zeile und setLocal EnableDelayedExpansion, dann könnte das Script folgendermaßen aussehen:

@echo off
setLocal EnableDelayedExpansion
set COUNT=0    
set COUNTMAX=0 

FOR /F  "eol=# tokens=1,2,3 " %%A IN ('find /c ";"liste.txt') DO set COUNTMAX=%%C

for /f "eol=# tokens=1 delims=;" %%j in (liste.txt) do (
   set /A COUNT +=1
   title %0 - !COUNT! Zeilen von !COUNTMAX! bearbeitet
   echo Tue etwas mit dem Token %%j
   REM der ping wird nur zur Verzögerung ausgeführt damit man der Titel Zeile besser verfolgen kann.
   ping -n 1 localhost >NUL
)

Einzelnachweise

Bearbeiten
  1. SS64.com: Variables: extract part of a variable (substring) Command line reference
  2. Microsoft: Windows IT Pro Center: Docs / Windows Server / Windows Commands / Commands by Server Role / setlocal
  3. https://technet.microsoft.com/de-de/library/cc754891(v=ws.10).aspx, Kommandozeilenbefehl Timeout bei Microsoft Technet