Batch - Flags im Exit-Code (Errorlevel) - per Bit-Operation mehrere Informationen in den Rückgabewert packen
Der Beitrag, inkl. Quelltext, ist Gemeinfrei (er darf ohne jegliche Einschränkung genutzt werden).
Sobald ein Konsoleprogramm beendet wird, kann es seinem Aufrufer einen numerischen Wert zurückgeben. Gleiches gilt für eine Batch-Datei und für eine Batch-Funktion.
Im Folgenden sollen Möglichkeiten aufgezeigt werden, um mehrere Zustände (im Folgenden Flags genannt) in diesen numerischen Rückgabewert zu packen. Der so aufbereitete Rückgabewert (engl. Returncode, auch Exitcode, Errorcode oder Errorlevel) soll es dem Aufrufer ermöglichen, die Zustände einzeln und in beliebiger Kombination zueinander abfragen zu können.
Der Rückgabewert lässt sich durchschleifen, indem die Batch über exit <Zahl> (also ohne die Option /b) beendet wird. Das hat jedoch den Nachteil, dass ein Aufruf der Batch von Hand sogleich auch die Konsole beendet. Eine Lösung könnte der folgende Wrapper sein, den die Windowsanwendung nutzen kann, um eine Batch zu starten:
Die Flags dürfen daher nur die Werte 1, 2, 4, 8, 16, 32, ... (also immer das Doppelte des vorherigen Wertes beginnend bei 1) erhalten.
Inhalt von MyBatch_1.cmd:
Inhalt von MyBatch_2.cmd:
Nachteil: Diese Lösung ist unflexibel, da die Funktion FncGetState den Status nur genau einmal zurückliefern kann (um die Abfrage des nächsten Flags vorzubereiten, löscht sie das aktuell abgefragte Flag aus _ReturnMask heraus). Zudem ist der Ansatz anfällig für Fehler: Wird in MyBatch_1.cmd ein weiteres (höherwertiges) Flag hinzugefügt und gesetzt, ohne MyBatch_2.cmd entsprechend anzupassen, so werden unter MyBatch_2.cmd plötzlich alle Flags wahr. Auch darf ein und dasselbe Flag unter Lösung 1 niemals zweimal gesetzt werden.
Warum Binärzahlen? Der deutsche Erfinder Konrad Zuse entwickelt und baut das, was heute allgemein als Computer bezeichnet wird; genauer gesagt die erste vollautomatische, programmgesteuerte und frei programmierbare, in binärer Gleitkommarechnung arbeitende Rechenanlage. Diese trägt die Bezeichnung Z3 und wird 1941 fertiggestellt. Seine erste Rechenanlage, die 1938 veröffentlichte Z1, enthält bereits alle Basisbausteine eines modernen Computers, wie z.B. Leitwerk, Programmsteuerung, Speicher, Mikrosequenzen und Gleitkommarithmetik. Entgegen der größtenteils mit elektromechanischen Relais arbeitenden Z3 war die blecherne Technik der Z1 rein mechanischer Natur.
Welche Zustände konnte aber eine mechanische Wippe - und an dieser Idee angelehnt - ein elektromechanisches Reais in Zuses Rechenanlage annehmen?: Hoch und runter / an und aus / 1 und 0. Auch heute noch arbeiten Computer nach diesem Prinzip. Und es ist genau das, was wir hier benötigen: Wir wollen einzelne Flags setzen (1) oder löschen (0 - also als nicht gesetzt markieren).
Wenn ein Computer nur 1 und 0 kennt, wie kommt er dann auf eine Zahl wie 2, 3, 4, und 5, etc.? Er setzt mehrere Bits (ein Bit kann den Zustand 0 oder 1 annehmen) nebeneinander und rechnet sie nach seinem eigenen binären System zusammen:
Mit vier Bits lassen sich die folgenden dezimalen Werte abbilden: 0 (binär: 0000), 1 (0001), 2 (0010), 3 (0011), 4 (0100), 5 (0101), 6 (0110), 7 (0111), 8 (1000), 9 (1001) bis 15 (1111). Eine Variable erhält in der Regel aber nicht nur 4 Bits, um ihren Wert zu verwalten, sondern wenigstens 8 Bits (ein Byte), meist 32 Bits (vier Byte) oder sogar 64 Bits (acht Byte). Die darstellbaren dezimalen Werte liegen bei nur einem Byte bereits bei 256 und nehmen mit jedem weiteren Byte rapide zu (zwei Byte=256*256=65536; drei Byte=256*256*256, etc.).
Mit jedem Bit, das dem Rückgabewert zur Verfügung steht, läßt sich ein Flag bedienen: Bit0 steht für unser erstes Flag, Bit1 für das zweite, etc. Lösung 1 ist bereits auf das Setzen binärer Flags ausgelegt – nur ist es dort nicht so offensichtlich. Sie nutzt einfach für jedes einzelne Flag keinen dezimalen Wert, der zwei oder mehr Bits gleichzeitig setzt. Pro Flag beschränkt sich das Beispiel zur Lösung 1 daher auf die dezimalen Werte 1 (binär: 0001), 2 (0010), 4 (0100) und 8 (1000), könnte aber auch über das vierte Bit hinaus gehen mit den Werten 16 (0001.0000), 32 (0010.0000), 64 (0100.0000), etc.
Der Rückgabewert hat gleich 32 Bits (0000.0000 0000.0000 0000.0000 0000.0000), die sich für unsere Flags nutzen lassen. Welche dezimalen Zahlen sich dahinter verbergen wird für unser Unterfangen irrelevant, sobald die Bits direkt gesetzt, gelöscht und abgefragt werden können. Genau das ermöglicht die folgende Funktion:
Inhalt von MyBatch_1.cmd:
Inhalt von MyBatch_2.cmd:
für das Setzen von Bit2 in der Variablen eine lesbare Syntax zu verwenden. Denn die "2" sagt nicht aus, wofür das Bit steht. In unserem Beispiel von oben, indem Bit2 für "ich habe Datei 2 gefunden" steht, wäre eine lesbare Syntax wie folgt denkbar:
Siehe dazu den folgenden Quellcode:
Bye, nz
Tipp: Wird der Inhalt in den Quelltextfenstern chaotisch angezeigt, kann es hilfreich sein dort auf den Link "Quelltext" (oben rechts) zu klicken. Es öffnet sich dann ein separates Fenster, welches in der maximierten Darstellung, je nach Bildschirmauflösung, den unerwünschten Zeilenumbruch unterbindet.
Sobald ein Konsoleprogramm beendet wird, kann es seinem Aufrufer einen numerischen Wert zurückgeben. Gleiches gilt für eine Batch-Datei und für eine Batch-Funktion.
Im Folgenden sollen Möglichkeiten aufgezeigt werden, um mehrere Zustände (im Folgenden Flags genannt) in diesen numerischen Rückgabewert zu packen. Der so aufbereitete Rückgabewert (engl. Returncode, auch Exitcode, Errorcode oder Errorlevel) soll es dem Aufrufer ermöglichen, die Zustände einzeln und in beliebiger Kombination zueinander abfragen zu können.
Inhaltsverzeichnis
1. Grundlagen zum Exit-Code
Beispiel, um einen Rückgabewert an seinen Aufrufer zu übermitteln:Inhalt von MyBatch.cmd:
@exit /b 5 ::Die Option /b sorgt dafür, dass hierbei nicht die aufrufende Konsole geschlossen wird
------------------------------
Aufruf der Batch in der DOS-Konsole:
c:\>MyBatch.cmd
c:\>echo %errorlevel%
------------------------------
Ausgabe: 5
Den Rückgabewert an eine Windowsanwendung übergeben
Wird die Batch aus einer Windowsanwendung heraus aufgerufen, so öffnet die dazugehörige API eine DOS-Konsole, die wiederum die Batch startet. Die Anwendung erhält danach den Rückgabewert der Konsole und nicht die der Batch.Der Rückgabewert lässt sich durchschleifen, indem die Batch über exit <Zahl> (also ohne die Option /b) beendet wird. Das hat jedoch den Nachteil, dass ein Aufruf der Batch von Hand sogleich auch die Konsole beendet. Eine Lösung könnte der folgende Wrapper sein, den die Windowsanwendung nutzen kann, um eine Batch zu starten:
Inhalt von Wrapper.cmd:
@call %*
@exit %errorlevel%
------------------------------
Aufruf: Wrapper.cmd MyBatch.cmd <Parameter1> <Parameter2> ...
Den Rückgabewert bei der Nutzung einer separaten cmd (auch über start) an den Aufrufer übergeben
Unter Nutzung der oben gezeigten Wrapper.cmd lässt sich auch hier der Rückgabewert durchschleifen:Aufruf aus der DOS-Konsole heraus:
c:\>MyBatch.cmd
c:\>echo %errorlevel%
Ausgabe: 5
c:\>cmd /c MyBatch.cmd
c:\>echo %errorlevel%
Ausgabe: 0
c:\>cmd /c Wrappper.cmd MyBatch.cmd
c:\>echo %errorlevel%
Ausgabe: 5
c:\>start /wait "" MyBatch.cmd
c:\>echo %errorlevel%
Ausgabe: -1073741510
c:\>start /wait "" Wrapper.cmd MyBatch.cmd
c:\>echo %errorlevel%
Ausgabe: 5
2. Ein Rückgabewert, der mehrere Zustände (Flags) verwaltet
Der Einfachheit halber soll ein Rückgabewert erzeugt werden, der angiebt , ob Datei1 gefunden wurde. Die Herausforderung besteht darin, dass derselbe Rückgabewert gleichzeitig auch eine entsprechende Aussage für Datei2 und Datei3 enthalten soll.Lösung 1: Addition dezimaler Zahlen 1, 2, 4, 8, 16, 32, ...
Eine einfache Möglichkeit besteht darin, jedem Flag einen dezimalen Wert zuzuweisen und diese Werte - sobald das jeweilige Flag gesetzt werden soll - dem Rückgabewert hinzuzufügen (z.B. Flag1 und Flag5 setzen: Rückgabewert=WertVonFlag1+WertVonFlag5). Damit die Flags später einzeln abfragbar sind, muss verhindert werden, dass durch die Addition aus versehen der Status eines anderen Flags im Rückgabewert beeinflusst wird.Die Flags dürfen daher nur die Werte 1, 2, 4, 8, 16, 32, ... (also immer das Doppelte des vorherigen Wertes beginnend bei 1) erhalten.
Inhalt von MyBatch_1.cmd:
set _MyErrorCode=0
if exist "c:\MyFile1.txt" set /a "_MyErrorCode=_MyErrorCode + 1" &rem "Datei 1 gefunden"-Flag setzen
if exist "c:\MyFile2.txt" set /a "_MyErrorCode=_MyErrorCode + 2" &rem "Datei 2 gefunden"-Flag setzen
if exist "c:\MyFile3.txt" set /a "_MyErrorCode=_MyErrorCode + 4" &rem "Datei 3 gefunden"-Flag setzen
exit /b %_MyErrorCode%
call MyBatch_1.cmd
set _ReturnMask=%errorlevel%
::Die Flags müssen in der entgegengesetzten Reihenfolge abgefragt werden, also beginnend mit dem höchsten Wert!
call :FncGetState _ReturnMask 4 && echo Datei 3 wurde gefunden.
call :FncGetState _ReturnMask 2 && echo Datei 2 wurde gefunden.
call :FncGetState _ReturnMask 1 && echo Datei 1 wurde gefunden.
goto :EOF
:FncGetState
setlocal EnableDelayedExpansion & set _#EL=0 & set /a _#1="!%~1! - %~2"
if %_#1% LSS 0 set _#EL=1 & set /a _#1+=%~2
endlocal & set "%~1=%_#1%" & exit /b %_#EL%
Nachteil: Diese Lösung ist unflexibel, da die Funktion FncGetState den Status nur genau einmal zurückliefern kann (um die Abfrage des nächsten Flags vorzubereiten, löscht sie das aktuell abgefragte Flag aus _ReturnMask heraus). Zudem ist der Ansatz anfällig für Fehler: Wird in MyBatch_1.cmd ein weiteres (höherwertiges) Flag hinzugefügt und gesetzt, ohne MyBatch_2.cmd entsprechend anzupassen, so werden unter MyBatch_2.cmd plötzlich alle Flags wahr. Auch darf ein und dasselbe Flag unter Lösung 1 niemals zweimal gesetzt werden.
Lösung 2: Per Bit-Operation die Flag-Bits direkt setzen, löschen und abfragen
Um sämtliche Probleme, die Lösung 1 mit sich bringt, mit einem Schlag zu lösen, muss man ein Grundverständnis für Binärzahlen entwickeln.Warum Binärzahlen? Der deutsche Erfinder Konrad Zuse entwickelt und baut das, was heute allgemein als Computer bezeichnet wird; genauer gesagt die erste vollautomatische, programmgesteuerte und frei programmierbare, in binärer Gleitkommarechnung arbeitende Rechenanlage. Diese trägt die Bezeichnung Z3 und wird 1941 fertiggestellt. Seine erste Rechenanlage, die 1938 veröffentlichte Z1, enthält bereits alle Basisbausteine eines modernen Computers, wie z.B. Leitwerk, Programmsteuerung, Speicher, Mikrosequenzen und Gleitkommarithmetik. Entgegen der größtenteils mit elektromechanischen Relais arbeitenden Z3 war die blecherne Technik der Z1 rein mechanischer Natur.
Welche Zustände konnte aber eine mechanische Wippe - und an dieser Idee angelehnt - ein elektromechanisches Reais in Zuses Rechenanlage annehmen?: Hoch und runter / an und aus / 1 und 0. Auch heute noch arbeiten Computer nach diesem Prinzip. Und es ist genau das, was wir hier benötigen: Wir wollen einzelne Flags setzen (1) oder löschen (0 - also als nicht gesetzt markieren).
Wenn ein Computer nur 1 und 0 kennt, wie kommt er dann auf eine Zahl wie 2, 3, 4, und 5, etc.? Er setzt mehrere Bits (ein Bit kann den Zustand 0 oder 1 annehmen) nebeneinander und rechnet sie nach seinem eigenen binären System zusammen:
0000
Bit3: ist es gesetzt, steht es für eine 8 <-'||'-> Bit0: ist es gesetzt, steht es für eine 1
Bit2: ist es gesetzt, steht es für eine 4 <--''--> Bit1: ist es gesetzt, steht es für eine 2
Mit jedem Bit, das dem Rückgabewert zur Verfügung steht, läßt sich ein Flag bedienen: Bit0 steht für unser erstes Flag, Bit1 für das zweite, etc. Lösung 1 ist bereits auf das Setzen binärer Flags ausgelegt – nur ist es dort nicht so offensichtlich. Sie nutzt einfach für jedes einzelne Flag keinen dezimalen Wert, der zwei oder mehr Bits gleichzeitig setzt. Pro Flag beschränkt sich das Beispiel zur Lösung 1 daher auf die dezimalen Werte 1 (binär: 0001), 2 (0010), 4 (0100) und 8 (1000), könnte aber auch über das vierte Bit hinaus gehen mit den Werten 16 (0001.0000), 32 (0010.0000), 64 (0100.0000), etc.
Der Rückgabewert hat gleich 32 Bits (0000.0000 0000.0000 0000.0000 0000.0000), die sich für unsere Flags nutzen lassen. Welche dezimalen Zahlen sich dahinter verbergen wird für unser Unterfangen irrelevant, sobald die Bits direkt gesetzt, gelöscht und abgefragt werden können. Genau das ermöglicht die folgende Funktion:
:FncBit ::Version 1.0.0
::Parameters: <-s | -S | -c | -C | -f | -F> <Var> <Bit or BitMask to set, clear or find>
::Options: -s set a bit; -S set a bit-combination by mask
:: -c clear a bit; -C clear a bit-mask
:: -f find a bit; -F find a bit-mask
::Example: set /a _MyVar=0 & call :FncBit -s _MyVar 0 5 (set bit 0 and 5 in _MyVar; 00100001=33)
:: call :FncBit -s _MyVar 1 (also set bit 1 in _MyVar; 00100011=35)
:: call :FncBit -c _MyVar 1 (clear bit 1 in _MyVar; 00100001=33)
:: set a bit-combination by mask: -S _MyVar 35 (set bits 0,1,5 in _MyVar)
:: also you can use a hex-format.. -S _MyVar 0x23 (set bits 0,1,5 in _MyVar)
:: find a bit ... call :FncBit -f _MyVar 0 5 && goto :FoundBit0and5inMyVar
:: ... or bit-mask: call :FncBit -F _MyVar 33 && goto :FoundBit0and5inMyVar
set "_FncBit_Mode=%~1" & set "_FncBit_Var=%~2" & shift & shift
:LOOP_FncBit
if "%_FncBit_Mode%" == "-s" set /a %_FncBit_Var%"|=(1 << %~1)"
if "%_FncBit_Mode%" == "-S" set /a %_FncBit_Var%"|=%~1"
if "%_FncBit_Mode%" == "-c" set /a %_FncBit_Var%"&=(~(1 << %~1))"
if "%_FncBit_Mode%" == "-C" set /a %_FncBit_Var%"&=(~ %~1)"
if "%_FncBit_Mode%" == "-f" set /a _FncBit_GetBit"=%_FncBit_Var% & (1 << %~1)"
if "%_FncBit_Mode%" == "-f" if "%_FncBit_GetBit%" == "0" exit /b 1
if "%_FncBit_Mode%" == "-F" set /a _FncBit_GetBits"=%_FncBit_Var% & %~1"
if "%_FncBit_Mode%" == "-F" set /a _FncBit_SearchBits"=%~1" &::ggf. hex nach dez umwandeln
if "%_FncBit_Mode%" == "-F" if not "%_FncBit_GetBits%" == "%_FncBit_SearchBits%" exit /b 1
if not "%~2" == "" shift && goto :LOOP_FncBit
exit /b 0
set _MyErrorCode=0
if exist "c:\MyFile1.txt" call :FncBit -s _MyErrorCode 1 &rem "Datei 1 gefunden"-Flag setzen
if exist "c:\MyFile2.txt" call :FncBit -s _MyErrorCode 2 &rem "Datei 2 gefunden"-Flag setzen
if exist "c:\MyFile3.txt" call :FncBit -s _MyErrorCode 3 &rem "Datei 3 gefunden"-Flag setzen
goto :END
<Hier muss die Funktion :FncBit hineinkopiert werden>
:END
exit /b %_MyErrorCode%
call MyBatch_1.cmd
set _ReturnMask=%errorlevel%
::Die Flags können in beliebiger Reihenfolge beliebig oft und in beliebiger Kombination abgefragt werden:
call :FncBit -f _ReturnMask 1 && echo Datei 1 wurde gefunden.
call :FncBit -f _ReturnMask 2 3 && echo Datei 2 und 3 wurden gefunden.
call :FncBit -f _ReturnMask 2 3 || echo Datei 2 und/oder 3 wurde^(n^) nicht gefunden.
rem Die Option -F, statt -f, ermöglicht die Angabe einer Bit-Kombination als einzelnen Wert:
rem Bit0 steht dezimal für 1, Bit1 für 2, Bit2 für 4 und Bit3 für 8, etc.
rem Im Folgenden soll geprüft werden, ob Bit2 und Bit3 gesetzt wurde: 4+8=12
call :FncBit -F _ReturnMask 12 && echo Datei 2 und 3 wurden gefunden.
goto :EOF
<Hier muss die Funktion :FncBit hineinkopiert werden>
3. Weiterführende Tipps im Umgang mit Flags
Den Bits einen sprechenden Namen geben
Ziel ist es, stattcall :FncBit -s Variable 2
für das Setzen von Bit2 in der Variablen eine lesbare Syntax zu verwenden. Denn die "2" sagt nicht aus, wofür das Bit steht. In unserem Beispiel von oben, indem Bit2 für "ich habe Datei 2 gefunden" steht, wäre eine lesbare Syntax wie folgt denkbar:
call :FncBit -s Variable %
Flag#FoundFile2
%
Siehe dazu den folgenden Quellcode:
@echo off & setlocal
rem Die folgende Zeile sollte sowohl in MyBatch_1.cmd, als auch in MyBatch_2.cmd eingefügt werden:
call :FncEnum Flag UnexpectedError FoundFile1 FoundFile2 FoundFile3
...
rem Flag setzen (in MyBatch_1.cmd):
if exist "c:\MyFile2.txt" call :FncBit -s _MyErrorCode %Flag#FoundFile2%
...
rem Flag abfragen (in MyBatch_2.cmd):
call :FncBit -f _ReturnMask %Flag#FoundFile2% && echo Datei 2 wurde gefunden.
...
goto :EOF
rem Die folgende Funktion sollte sowohl in MyBatch_1.cmd, als auch in MyBatch_2.cmd eingefügt werden:
:FncEnum {{comment_single_line_remark:0}}
::Parameters: [-a] <Prefix> <Name1> <Name2> ..
::Example: call :FncEnum Enum1 a b & call :FncEnum Enum1 -a c d e f & call :FncEnum Enum2 c d e f
:: echo %Enum1#a% %Enum1#b% %Enum1#c% %Enum1#d% %Enum1#e% %Enum1#f% (out: 0 1 2 3 4 5)
:: echo %Enum2#c% %Enum2#d% %Enum2#e% %Enum2#f% (out: 0 1 2 3)
:: echo next free enum of Enum1=%Enum1#% and Enum2=%Enum2#% (out: 6 and 4)
if not "%~1" == "-a" ( set "%~1#=0" ) else ( shift )
set "_FncEnum_Prefix=%~1#" & shift
:LOOP_FncEnum_SetEnum
set /a "%_FncEnum_Prefix%%~1=%_FncEnum_Prefix%" & set /a "%_FncEnum_Prefix%+=1" & shift
if not "%~1" == "" goto :LOOP_FncEnum_SetEnum
exit /b 0
Bit0 als allgemeines Fehlerbit verwenden
In nahezu allen Anwendungsfällen bietet sich ein Flag für den allgemeinen Fehlerzustand bzw. für den "unerwarteten (innerhalb der Batch nicht näher spezifizierten) Fehler" an. Bit0 ist ideal dafür, weil das Flag unabhängig von der Anzahl der übrigen Flags und der Variablengröße immer am selben Platz zu finden ist.Bye, nz
Bitte markiere auch die Kommentare, die zur Lösung des Beitrags beigetragen haben
Content-ID: 97143
Url: https://administrator.de/contentid/97143
Ausgedruckt am: 21.11.2024 um 22:11 Uhr
1 Kommentar