Magic numbers in Windows-Batch-Dateien

Eine Batch-Datei parst ein Datum. Das Parsen des Datums geht gut bis Ende Juli. Im August und September bricht das Skript zunächst unbemerkt ab.

Ab Oktober läuft das Skript dann wieder durch.

Bis nächstes Jahr im August…

Was ist passiert?

Nach allgemeiner Auffassung gibt es in Batch-Skripten nur mit Hausmitteln keine einfache Möglichkeit das aktuelle Datum numerisch zu erfassen und auszuwerten. Man kann sich damit behelfen, die automatisch gesetzte Variable %date% zu verwenden, also z.B. mit

set month=%date:~3,2%

Damit legt man sich natürlich auf ein bestimmtes Datumsformat fest, bei dem der Monat an dritter Stelle auftritt und mit einer führenden Null notiert wird (“01.01.2013”). Das Datumsformat kann aber auf andern System oder eventuell unter einem andern Nutzerkonto verschieden sein.

Um der Problematik mit den unterschiedlichen Formaten zumindest ansatzweise zu begegnen prüft das Skript deshalb das Format auf Konsistenz ab. So bewegen sich Tage im Bereich zwischen 1 und 31, Monate zwischen 1 und 12. Dazu kommt folgende Konstruktion zum Einsatz:

if %day% GEQ 1 ( if %day% LEQ 31 (
	if %month% GEQ 1 ( if %month% LEQ 12 (
		if %year% GEQ 2000 ( if %year% LEQ 3000 (
			set dateCheckOK=1
		))
	))
))

Wie gesagt geht das Abprüfen des Monats zunächst gut. Laut Dokumentation bevorzugen die Vergleichsoperatoren LEQ und GEQ die numerische Auswertung des Ausdrucks, sodaß auch 02 GEQ 1 wahr ist.

Ab August ist jedoch der Wurm drin. Exemplarisch führt der folgende Code

if 07 GEQ 1 echo "07 groesser als 1"
if 08 GEQ 1 echo "08 groesser als 1"
if 09 GEQ 1 echo "09 groesser als 1"
if 10 GEQ 1 echo "10 groesser als 1"

nur zweimal zur Ausgabe

07 groesser als 1
10 groesser als 1

Der Grund dafür liegt in der Art- und Weise wie der Kommando-Interpreter Zahlen in Literalen erkennt (nach einem optionalen Minuszeichen):

  • beginnen sie mit “0x” beginnen sind sie hexadezimal
  • beginnen sie mit “0” sind sie oktal
  • beginnen sie mit 1-9 sind sie dezimal

Die Literale “08” und “09” werden somit nicht als dezimal, sondern als ungültige Oktalzahlen erkannt! Die Anweisung

set /A a=09

führt zum Fehler

Ungültige Zahl. Numerische Konstanten sind entweder dezimale (17),
hexadezimale (0x11) oder oktale (021) Zahlen.

Tritt dieser Fall in einer if – Bedingung auf, wird allerdings kein Fehler erzeugt, sondern tückischerweise automatisch auf String-Vergleich geschaltet!

Für den Fall, daß die Anzahl an möglichen führenden Stellen fest ist, sollte man immer einen String-Vergleich erzwingen. Das Beispiel der Datumsprüfung ergibt somit:

if "%day%" GEQ "01" ( if "%day%" LEQ "31" (
  if "%month%" GEQ "01" ( if "%month%" LEQ "12" (
    if "%year%" GEQ "2000" ( if "%year%" LEQ "3000" (
      set dateCheckOK=1
    ))
  ))
))

Zum Weiterlesen:

  • microsoft.com: “The octal notation can be confusing. For example, 08 and 09 are not valid numbers because 8 and 9 are not valid octal digits.”
  • Forum-Eintrag: “One other major difference – Numeric parsing is abandoned when an invalid digit is detected and IF uses a string comparison.”