Windows Batch-Dateien: Seltsames Verhalten des IF-Befehls

Windows[English]Blog-Leser Frank hat mich auf ein Problem unter Windows aufmerksam gemacht, zu dem ich keine wirkliche Erklärung habe. Ursprünglich ging es darum, dass der IF-Befehl in Batch-Dateien bei Vergleichen falsche Werte zurück lieferte. Also hat Frank das Ganze noch mit der PowerShell getestet und dort ähnliches festgestellt. Nur ein Testprogramm, verfasst mit VB .NET liefert die erwarteten Ergebnisse. Da das Batch-Programm auch unter Windows 7 die gleichen, fehlerhaften, Ergebnisse liefert, gehe ich mal davon aus, das Frank und ich da irgend etwas übersehen habe. Ich stelle das Problem mal hier im Blog ein – vielleicht fällt einem Leser etwas auf.


Anzeige

Problem: Fehlerhafte Batch-Auswertung

Blog-Leser Frank S. hat mich kürzlich per E-Mail kontaktiert und schrieb mir von seinem Problem in der Verwendung des IF-Befehls bei Batch-Dateien. Frank schrieb:

ich stolpere dauernd über seltsame Sachen in Windows. Heute wollte ich ein Batch-Programm implementieren, bei dem ich Buchstaben auf „Größer" oder „Kleiner" vergleichen muss. Dabei können sowohl Klein-, als auch Großbuchstaben vorkommen.

Das Programm verhielt sich völlig absurd, so dass ich der Sache auf den Grund ging und schließlich feststellte, dass der „if"-Befehl mit den erweiterten Vergleichsbefehlen sich so verhält, als ob ich die Option „/I" verwendet hätte, was ich nicht habe. Ich habe hier ein kleines Beispielprogramm erstellt, dass das seltsame Verhalten zeigt.

Frank hat mir den nachfolgenden Quellcode des Batch-Scripts mitgeliefert.


@echo off
setlocal enableextensions
if A == a (
echo A == a : True [Wrong!]
) else (
echo A == a : False [OK]
)
if A EQU a (
echo A EQU a : True [Wrong!]
) else (
echo A EQU a : False [OK]
)
if A LSS b (
echo A LSS b : True [OK]
) else (
echo A LSS b : False [Wrong!]
)
if A LEQ b (
echo A LEQ b : True [OK]
) else (
echo A LEQ b : False [Wrong!]
)
if A GTR b (
echo A GTR b : True [Wrong!]
) else (
echo A GTR b : False [OK]
)
if A GEQ b (
echo A GEQ b : True [Wrong!]
) else (
echo A GEQ b : False [OK]
)
if a LSS B (
echo a LSS B : True [Wrong!]
) else (
echo a LSS B : False [OK]
)
if a LEQ B (
echo a LEQ B : True [Wrong!]
) else (
echo a LEQ B : False [OK]
)
if a GTR B (
echo a GTR B : True [OK]
) else (
echo a GTR B : False [Wrong!]
)
if a GEQ B (
echo a GEQ B : True [OK]
) else (
echo a GEQ B : False [Wrong!]
)
if Z LSS a (
echo Z LSS a : True [OK]
) else (
echo Z LSS a : False [Wrong!]
)
if ALPHA LSS alpha (
echo ALPHA LSS alpha : True [OK]
) else (
echo ALPHA LSS alpha : False [Wrong!]
)
if ALPHA LEQ alpha (
echo ALPHA LEQ alpha : True [OK]
) else (
echo ALPHA LEQ alpha : False [Wrong!]
)
if ALPHA GTR alpha (
echo ALPHA GTR alpha : True [Wrong!]
) else (
echo ALPHA GTR alpha : False [OK]
)
if ALPHA GEQ alpha (
echo ALPHA GEQ alpha : True [Wrong!]
) else (
echo ALPHA GEQ alpha : False [OK]
)
Pause

Frank schrieb dazu, dass er diese Batch-Datei unter Windows 10 mit dem neuesten Patch-Stand (V10.0.19044.2006) ausgeführt und folgende Ausgabe erhalten habe.


A == a : False [OK]
A EQU a : False [OK]
A LSS b : True [OK]
A LEQ b : True [OK]
A GTR b : False [OK]
A GEQ b : False [OK]
a LSS B : True [Wrong!]
a LEQ B : True [Wrong!]
a GTR B : False [Wrong!]
a GEQ B : False [Wrong!]
Z LSS a : False [Wrong!]
ALPHA LSS alpha : False [Wrong!]
ALPHA LEQ alpha : False [Wrong!]
ALPHA GTR alpha : True [Wrong!]
ALPHA GEQ alpha : True [Wrong!]

Die Extended-Vergleiche ignorieren die Groß- oder Kleinschreibweise. Das Zeichen a ist in ANSI und Unicode numerisch größer, als ein B oder ein A. Frank schreibt, dass sich die Befehle so verhalten, als ob die Option /I angegeben worden wäre. Allerdings ist unklar, warum ALPHA GTR alpha das Ergebnis True ergibt. Einzig die Vergleiche == und EQU verhalten sich korrekt.


Anzeige

IF-Vergleich

Ich habe das Ganze unter Windows 7 SP1 laufen lassen und erhalte das gleiche Ergebnis – es hängt also nicht von Windows 10 ab. Da muss irgend ein Denkfehler vorliegen. Frank schrieb mir auf die Frage nach einem Denkfehler:

Das war auch mein erster Gedanke. Aber ich finde nichts. Ich habe das Ganze jetzt mit 3 verschiedenen Programmiersprachen getestet:

1. Batch
2. PowerShell
3. VB .NET

Die beiden Skript-Sprachen liefern beide dasselbe falsche Ergebnis. Es ist also kein Problem von Batch allein. VB .NET macht es richtig.

Hier noch die Quelltexte der betreffenden Testprogramme, die die Ergebnisse mit einer Kodierung für CodePage850 in ein Ausgabefenster schreiben.

Batch-Programm

@echo off
setlocal enableextensions enabledelayedexpansion

set alphabet=0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZÄÖÜäöüß

set pos=0

:mainLoop

set char=!alphabet:~%pos%,1!

if A%char% == A goto :endProc

echo.

set pos2=0

:innerLoop

set char2=!alphabet:~%pos2%,1!

if A%char2% == A goto :nextMain

call :testChar %char% %char2%

set /a pos2=pos2+1

goto :innerLoop

:nextMain

set /a pos=pos+1

goto :mainLoop

:testChar

if %1 lss %2 echo %1 ^<  %2

if %1 leq %2 echo %1 ^<= %2

if %1 gtr %2 echo %1 ^>  %2

if %1 geq %2 echo %1 ^>= %2

exit /b

:endProc

PowerShell-Script

function Test-Char
{
Param
(
[Parameter(Mandatory=$true, Position=0)]
[string] $Char1,
[Parameter(Mandatory=$true, Position=1)]
[string] $Char2
)
if ($Char1 -clt $Char2) {
Write-Output "$Char1 <  $Char2"
}
if ($Char1 -cle $Char2) {
Write-Output "$Char1 <= $Char2" } if ($Char1 -cgt $Char2) { Write-Output "$Char1 >  $Char2"
}
if ($Char1 -cge $Char2) {
Write-Output "$Char1 >= $Char2"
}
}
$PSDefaultParameterValues['Out-File:Encoding'] = 'oem'
$alphabet="0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZÄÖÜäöüß"
for($pos1=0; $pos1 -lt $alphabet.Length; $pos1++) {
Write-Output ""
for($pos2=0; $pos2 -lt $alphabet.Length; $pos2++) {
Test-Char $alphabet.Substring($pos1, 1) $alphabet.Substring($pos2, 1)
}
}

VB .NET


Imports System
Module Program
Sub Main(args As String())
Dim alphabet As String = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZÄÖÜäöüß"
For pos1 As Integer = 0 To alphabet.Length - 1
Console.WriteLine()
For pos2 As Integer = 0 To alphabet.Length - 1
TestChar(alphabet.Substring(pos1, 1), alphabet.Substring(pos2, 1))
Next pos2
Next pos1
End Sub
Private Sub TestChar(char1 As String, char2 As String)
If char1 < char2 Then _
Console.WriteLine($"{char1} <  {char2}")
If char1 <= char2 Then _
Console.WriteLine($"{char1} <= {char2}") If char1 > char2 Then _
Console.WriteLine($"{char1} >  {char2}")
If char1 >= char2 Then _
Console.WriteLine($"{char1} >= {char2}")
End Sub
End Module

Daher die Frage an die Leserschaft, ob jemandem ad-hoc dazu eine Erklärung einfällt.


Anzeige

Dieser Beitrag wurde unter Windows abgelegt und mit , verschlagwortet. Setze ein Lesezeichen auf den Permalink.

20 Antworten zu Windows Batch-Dateien: Seltsames Verhalten des IF-Befehls

  1. Willy B. sagt:

    Müssten die Buchstaben nicht in Anführungszeichen gesetzt werden?
    z.B. if "A" == "a"
    oder auch if 'A' == 'a'

    • 1ST1 sagt:

      Nein, das muss in diesen verblüffenden (!) Beispielen nicht unbedingt sein, weil hier konstante Inhalte miteinander verglichen werden, aber es schadet hier auch nicht.

      Interessant wird es erst, wenn man Variablen wie %1 oder %variable% miteinander vergleicht, die können nämlich auch leer sein. Dann helfen tatsächlich beliebige Zusatzzeichen wie x%1 oder x%variable% oder Anführungszeichen, Klammern oder was auch immer man da nehmen mag, die man natürlich auch auf der anderen Seite des Vergleichs einfügen muss.

      if x%1 == x/? goto help

      Würde der Batch kein Parameter übermittelt, würde der Batchinterpreter ohne die "x" nämlich versuchen "if == /? goto help" zu verstehen und scheitert. Mit dem "x" wird "if x == x/? goto help" interpretiert, was das dazu führt, das nicht nach help gesprungen wird.

      • Paul sagt:

        a LSS B : True [Wrong!]
        a LEQ B : True [Wrong!]
        a GTR B : False [Wrong!]
        a GEQ B : False [Wrong!]
        Z LSS a : False [Wrong!]

        "Klein a" ist in der Sortierungfolge kleiner als "groß B",
        oder?
        Dann ist "a GTR B : False" das gewünschte Ergebnis.

        Ja. in der ASCI-Tabelle hat groß A 41h und klein a 61h.
        Aber welcher Anwender kennt die Hex werte der ACSI
        Tabelle?
        In der "Natur" kommen Großbuchstaben vor den kleinen.

        Bau mal da noch mal
        if A GTR a
        etc. ein.
        Dann wird das klarer weil so nur ein Wert geändert wird.

        War aber eine echt interessante Frage.

  2. Sascha Bertelt sagt:

    Hallo Günter,

    ich kenne mich kaum (noch) mit der Konsole aus, aber ein Wenig mit PowerShell.
    Normalerweise sind alle Vergleichsoperationen immer case-IN-sensitiv.
    z.B. -eq, -gt, etc.

    Wenn man ein case-sensitive benötigt, ist der Syntax des Operators anders!
    z.B. -ceq, -cgt, etc

    Gruß
    Sascha

  3. Paul sagt:

    https://ss64.com/nt/if.html

    "IF will only parse numbers when one of (EQU, NEQ, LSS, LEQ, GTR, GEQ) is used.
    The == comparison operator always results in a string comparison."
    ..
    "
    Correct numeric comparison:
    IF 2 GEQ 15 echo "bigger"
    "

    "Using parentheses or quotes will force a string comparison:
    IF (2) GEQ (15) echo "bigger"
    IF "2" GEQ "15" echo "bigger"
    "
    This behaviour is exactly opposite to the SET /a command where quotes are required.

    Im Grunde müßtest Du etwas "asc2num" machen ehe das ins IF geht…

    Sind "a" und "b" nicht auch (hex) Nummern?

    Aber interessant das das in Powershell auch so ist.
    Ich dachte das wäre mal ver vernüftiges.

    "UTF-8" ist in Batches auch sehr "lustig"…

    Ich würde die Wert im IF immer in "" setzten.
    Weil Fehler im IF sehr mühesam zu finden sind.

    • Frank sagt:

      In den Beispielen soll ein String-Vergleich gemacht werden, kein numerischer Vergleich. Es muss also kein "asc2num" gemacht werden. Der Vergleich mit EQU, NEQ, GTR, GEQ, LSS, und LEQ kennt keine Hex-Zahlen, nur Dezimalzahlen.

      Im Batch ist es egal, ob man

      if Q GTR q

      oder

      if "Q" GTR "q"

      oder

      if (Q) GTR (q)

      eingibt. Das Ergebnis ist immer gleich falsch.

      Besonders erstaunlich ist, dass PowerShell denselben Fehler macht.

  4. MOM20xx sagt:

    tja microsoft ist sich wohl selbst nicht sicher. aktuelles windows 10

    help if:

    Wenn die Befehlserweiterungen aktiviert sind, wird der
    IF-Befehl folgendermaßen verändert:

    IF [/I] Zeichenfolge1 Vergleichsoperator Zeichenfolge2 Befehl
    IF CMDEXTVERSION Zahl Befehl
    IF DEFINED Variable Befehl

    Mögliche Vergleichsoperatoren:

    EQU – gleich
    NEQ – nicht gleich
    LSS – kleiner als
    LEQ – kleiner als oder gleich
    GTR – größer als
    GEQ – größer als oder gleich

    Die /I-Option wird angegeben, um die Groß-/Kleinschreibung
    beim Vergleich zu ignorieren. Die /I-Option kann auch in der Form
    "Zeichenfolge1==Zeichenfolge2" von "IF" verwendet werden.
    Diese Vergleiche sind allgemein, das heißt, wenn beide Zeichenfolgen
    nur aus Ziffern bestehen, werden die Zeichenfolgen in Zahlen
    umgewandelt, und es wird ein numerischer Vergleich durchgeführt.

    wozu hab ich dann /I ?

  5. M.D. sagt:

    Wirklich interessant, hab ich bisher nicht so verwendet und bin daher auch nicht drüber gestolpert. Interessehalber hab ich das gerade mal in der Powershell in Version 7.2.6 kurz ausprobiert und kann das seltsame Verhalten nachvollziehen.

    In der Hilfe dazu steht allerdings auch folgendes (Zitat):

    # Sorting order comparison
    'a' -lt 'z' # True; 'a' comes before 'z'
    'macOS' -ilt 'MacOS' # False
    'MacOS' -ilt 'macOS' # False
    'macOS' -clt 'MacOS' # True; 'm' comes before 'M'

    Dass 'm' vor 'M' kommt, ist mir so ebenfalls neu. Das hat demnach dann nichts mit dem ASCII-Code zu tun, denn da kommt 'M' vor 'm'.

    Was auch immer die sich dabei gedacht haben, das bricht so ziemlich mit jeder Konvention.

  6. Frank sagt:

    Das mit der PowerShell-Hilfe ist wirklich interessant. Seit wann kommt 'm' vor 'M'? Ich habe mal das Folgende in die PowerShell eingegeben:

    $alphabet = '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'Ä', 'Ö', 'Ü', 'ä', 'ö', 'ü', 'ß'

    $alphabet -cgt 'W'

    Das sollte alle Zeichen aus der Variablen "$alphabet" ausgeben, die mit Fallunterscheidung größer als 'W' sind. Ergebnis:

    x
    y
    z
    X
    Y
    Z

    Wenn man

    $alphabet -igt 'W'

    eingibt, sollten alle Zeichen ausgegeben werden, die ohne Ansehen von Groß- oder Kleinschreibung größer als 'W' sind. Ergebnis:

    x
    y
    z
    X
    Y
    Z

    Völlig identisch! Mit "-igt" erwarte ich auch genau das, aber eben nicht mit "-cgt".

    Seltsamerweise werden auch noch 'Ä' und 'ä' als gleich zu 'a' angesehen, 'Ö' und 'ö' als gleich zu 'o', 'Ü' und 'ü' als gleich zu 'u' und 'ß' als gleich zu 's'.

    • M.D. sagt:

      Das Thema bzw. der "Fehler" ist nicht neu, siehe den Kommentar unter:

      PowerTip: Use PowerShell to Perform Case-Sensitive Comparison

      Interessanterweise gibt es zu seiner Frage keine Antwort und im Netz findet man ziemlich wenig zu dieser Problematik. Es scheint so zu sein, dass zu wenige diese Art des Vergleichs verwenden oder aber über das Verhalten stolpern und es mangels Verständnis und Lösung irgendwie umgehen.

      Falls aber doch irgendwelche Script-Programmierer dieses ungewöhnliche Verhalten adaptiert haben sollten, könnte es im Desaster enden, wenn Microsoft das jetzt per Patch auf das eigentlich erwartete Verhalten ändert. Nicht auszudenken, wenn plötzlich Vergleiche in die andere Richtung weisen und falsche Dateien gelöscht, umbenannt oder verschoben werden oder falsche Berechnungen ausgeführt werden.

    • Heiko sagt:

      Zitat: Seltsamerweise werden auch noch 'Ä' und 'ä' als gleich zu 'a' angesehen, 'Ö' und 'ö' als gleich zu 'o', 'Ü' und 'ü' als gleich zu 'u' und 'ß' als gleich zu 's'.

      Diesbezüglich würde ich jetzt spontan darauf tippen, dass im zu grunde liegenden Code ohne Berücksichtigung der Sprache/Kultur verglichen wird. Das führt dann dazu, das Apostroph, etc. ignoriert werden. Das wärte also ein Vergleich mit "InvariantCultureIgnoreCase" (https://learn.microsoft.com/en-us/dotnet/api/system.stringcomparer.invariantcultureignorecase).

      • Paul sagt:

        Die Console kennt eine Code page.
        Auch sind ihr ANSI ASCI UTF-8 nicht fremd.

        C:\Users\Public>mode con: cp
        Status von Gerät CON:
        ———————
        Codepage: 850

        Aber stimmt, zum Vergleichen wird noch mal eine Andere Tabelle verwendet. Sonst wäre ja "A" kleiner als "a" was nicht dem erwarteten entspräche.

        Es wäre schon fatal, wenn ein batch in girechenland nicht mehr oder anders funktionieren würde.
        Das ist ja kein Excel.

    • P.B. sagt:

      Frank, des Rätsels Lösung ist eigentlich, dass Powershell im String Vergleich gewollt anders agiert, anders zumindest als im erzwungenen Char Vergleich.

      Willst du explizit numerisch Vergleichen – Sidenote, Powershell nutzt die Unicode Nummer – musst du den beiden Variablen sagen, dass es Typ Char sein soll. Ansonsten kann es je nach Typ dazu kommen, dass Powershell eben einen String Vergleich anstellt.

      Der Stringvergleich nutzt hier die sogenannte Alphabetical Order (u.A) und sortiert auf Basis der Collation sogar noch nach gleicher Bedeutung – dass ist dahingehend richtig und auch wichtig zu wissen, wenn man automatisiert verarbeiten möchte und in verschiedenen Sprachen verschiedene Zeichen quasi gleiche Bedeutung haben. Beim Sortieren bspw. sind im deutschen ä, ö und ü jeweils zu ihren nicht Umlauten Standard Zeichen zugehörig, ß hingegen wird zum ss zugeordnet – und so sollte auch sotiert werden bei Strings.
      Bei optisch gleichen Zeichen kann das sehr verwirrend im Vergleich zu optisch nicht gleichen Zeichen sein, wenn man da nicht explizit drauf achtet im Code!

      Im String Vergleich ist "a" btw. auch kleiner "A", während es im Ascii bzw. Unicode Nummernvergleich anders herum ist.
      Es gibt einen recht guten Wikipedia Eintrag dazu.

      Um das mal in einem Beispiel zu verdeutlichen:
      —————————————————
      [string]$a = [char]::ConvertFromUtf32(0x03A9)
      [string]$b = [char]::ConvertFromUtf32(0x2126)

      [char]$a -ceq [char]$b
      [string]$a -ceq [string]$b

      [char]::ConvertFromUtf32(0x03A9)
      [char]::ConvertFromUtf32(0x2126)
      —————————————————
      Ergibt:
      False
      True
      Ω

      —————————————————
      [string]$a = [char]::ConvertFromUtf32(0x0041)
      [string]$b = [char]::ConvertFromUtf32(0x0061)

      [char]$a -clt [char]$b
      [string]$a -cgt [string]$b

      [char]::ConvertFromUtf32(0x0041)
      [char]::ConvertFromUtf32(0x0061)
      —————————————————
      Ergibt hingegen:
      True
      True
      A
      a
      -> man beachte hier -clt beim Char- und -cgt beim String Vergleich – beides ist aber wahr.
      Noch spannender wird das übrigens, wenn noch eine unbewusste Typumwandlung ins Spiel kommt, denn dann wird es richtig undurchsichtig.

      Zu der Batch Thematik kann ich nicht viel sagen – bin eher bei Powershell unterwegs.

  7. Jsson sagt:

    https://www.windowspro.de/tipp/vergleichsoperatoren-und-ifelse-batch-dateien

    Zitat: "Der Vergleich von Zeichenketten betrachtet in den meisten Programmiersprachen jene als größer, deren erstes nicht übereinstimmendes Zeichen einen größeren Wert im verwendeten Zeichensatz hat. Microsoft geht mit cmd.exe hier andere Wege und wertet Großbuchstaben größer als Kleinbuchstaben."

  8. sumpfnagel sagt:

    Ich habe die starke Vermutung, dass die Befehle EQU, LSS, GEQ usw. gar nicht für Strings definiert sind und somit nur zufällig auch mal das richtige Ergebnis liefern. Demnach wäre für Strings nur == erlaubt, die anderen nur für Zahlen.

    Hier eine beliebige Quelle mit dem Hinweis: https://www.delftstack.com/howto/batch/batch-string-compare/

  9. Michel Py sagt:

    Ich bin nicht mehr 100% sicher, aber in Batch Programmierung sollte man Leerschläge nicht wie in andere Programmiersprachen für Lesbarkeit benützen !
    Ich würde also nie IF "A" == "a" in ein Batch Script schreiben,
    sondern IF "A"=="a" sonst passieren merkwürdige unlogische Effekten :-)

    • Paul sagt:

      Ja, man sollte z.B.
      set "pw=geheim"
      schreiben,
      set pw=geheim
      kann jmd. sehr ärgern da er bei vielen Editoren dsa trailing " " nicht erkennt…
      vorsicht: Set in "" hat im Ablauf eine andere Funktion als ohne ""…

      Man muß schon wissen was man tut…
      Niemand hat gesagt, das Batch einfach zu programmieren ist.

      Hier z.B.:
      C:\Users\Public>set pw= geheim

      C:\Users\Public>echo #%pw%#
      # geheim #

      Natürlich sollte man sowieso ein Passwort niiiiiie in einem batch stehen haben!

      Aber ich glaube nicht, dass das hier das Problem ist.

      Sondern u.A. das bei "if" Großbuchstaben größer sind als Kleinbuchstaben,
      obwohl ASCII/ANSI das anders definiert.

  10. McAlex777 sagt:

    Ohne das Beispiel im Detail analysiert zu haben:
    IF macht Probleme mit Rückgabewerten bei z.B. choice.

    ==============================

    @echo off
    set a=x

    if [%a%] == [x] (
    choice /M "Wollen Sie das wirklich?"
    echo ERGEBNIS: %errorlevel%
    )

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert

Hinweis: Bitte beachtet die Regeln zum Kommentieren im Blog (Erstkommentare und Verlinktes landet in der Moderation, gebe ich alle paar Stunden frei, SEO-Posts/SPAM lösche ich rigoros). Kommentare abseits des Themas bitte unter Diskussion.

Du findest den Blog gut, hast aber Werbung geblockt? Du kannst diesen Blog auch durch eine Spende unterstützen.