neonzero
Goto Top

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).
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.


back-to-top1. 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
back-to-topDen 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> ...

back-to-topDen 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

back-to-top2. 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.

back-to-topLö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%
Inhalt von MyBatch_2.cmd:
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.

back-to-topLö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 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:
: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
Inhalt von MyBatch_1.cmd:
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%
Inhalt von MyBatch_2.cmd:
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>

back-to-top3. Weiterführende Tipps im Umgang mit Flags

back-to-topDen Bits einen sprechenden Namen geben
Ziel ist es, statt
    call :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
back-to-topBit0 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

Content-Key: 97143

Url: https://administrator.de/contentid/97143

Printed on: April 23, 2024 at 19:04 o'clock

Member: NeonZero
NeonZero Sep 22, 2008 at 16:36:52 (UTC)
Goto Top
In der DOS-Hilfe zu „set /?“ ist ein Hinweis von MS zu finden, dass set /a neben dezimalen Zahlen und hex (0x<hex-Wert>) bzw. okt (0<okt-Wert>) auch mit der binären Schreibweise zurechtkommt (0b<bin-Wert>). Das mit den binären Zahlen wollte mir nicht gelingen. Leider konnte ich dazu im Netz nichts finden. Hat das jemand von euch schon einmal geschafft? Wenn ja, wie? Ein kleiner Code-Schnipsel wäre nicht schlecht, damit ich das in die Anleitung einarbeiten kann.

Bye, nz