[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
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 .NETDie 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
Müssten die Buchstaben nicht in Anführungszeichen gesetzt werden?
z.B. if "A" == "a"
oder auch if 'A' == 'a'
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.
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.
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
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.
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.
Was ergibt denn
If 64 gtr a
Etc.
64 ist klar eine Nummer.
Was ist s
Schönes Problem
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 ?
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.
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'.
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.
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).
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.
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.
Danke für die Erläuterungen!
Einerseits beruhigt es, weil es eine Erklärung für das Verhalten gibt. Andererseits erschreckt es auch, denn wenn man sich mal die von Dir angedeuteten Seiten der Wikipedia ansieht, dann ist das ein schwer zu durchdringendes und zu verstehendes Thema. Ich denke Du beziehst Dich auf:
https://de.wikipedia.org/wiki/Alphabetische_Sortierung
und
https://de.wikipedia.org/wiki/Unicode_Collation_Algorithm
Allein die Beispiele der deutschsprachigen Sortierung sind in meinen Augen eher eine Warnung, den String-Vergleich in der PowerShell besser nicht anzuwenden.
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."
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/
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 :-)
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.
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%
)