Batch-Tricks

Batch-Tricks: Rekursion

Hach ja, Rekursion, das Lieblingstierchen jedes Programmierers. Sicher ist sowas auch in Batchdateien möglich (Ich versuche übrigens immer noch Turingvollständig­keit nachzuweisen :-)).

Der erste zaghafte Test wäre erstmal eine unendliche Rekursion:

@ECHO OFF
:JUMP
CALL :JUMP

Und siehe da, sie funktioniert:

******  B A T C H   R E C U R S I O N  exceeds STACK limits ******
Recursion Count=599, Stack Usage=90 percent
******       B A T C H   PROCESSING IS   A B O R T E D ******

Genau das, was wir haben wollten. Etwas sinnvolleres als einen Stacküberlauf hatten wir uns ohnehin nicht erhofft. Also offensichtlich kann cmd Rekursion.

Dann sollten wir das auch mal mit einem zumindest ansatzweise praxisbezogenem Problem testen: Fakultäten. Ungeachtet dessen, daß man die besser iterativ berechnet. Wir wollen aber nur sicherstellen, daß die Rekursion vernünftig funktioniert:

@ECHO OFF
SETLOCAL ENABLEEXTENSIONS ENABLEDELAYEDEX­PANSION

CALL :fac %1
ECHO %RETURN%

:end
ENDLOCAL
GOTO :EOF

:fac
IF %1==0 (
        SET RETURN=1
        GOTO :EOF
)
SET /A TEMP=%1 – 1
CALL :fac %TEMP%
SET /A RETURN*=%1
GOTO :EOF

Wir brauchen hier leider eine temporäre Variable, da cmd keine Berechnungen ohne SET /A erlaubt, aber ansonsten sieht es in etwa so aus, wie es sollte. Der Rekursionsabbruch wurde am Anfang des Unterprogramms durch ein IF abgefangen, leider gibt es keine funktionalen Nettigkeiten wie verschiedene Funktionsdefi­nitionen hier.

Und funktioniert das nun auch? Aber sicher:

> fac 5
120
> fac 10
3628800

Mein Taschenrechner sagt mir sogar, daß die Werte richtig sind. 12! ist leider die höchste Fakultät, die man damit berechnen kann, da wir auf 32-bittige vorzeichenbehaftete Ganzzahlen beschränkt sind. Ein kleiner Fehler ist noch vorhanden, wenn man negative Zahlen als Argument angibt (wieder eine unendliche Rekursion). Das ist allerdings in der angehängten Version behoben, ebenso bekommt man in selbiger eine hilfreiche Nachricht, wenn man die Batch ohne Parameter aufruft.

Und nur so nebenbei, eine nette Variante, Fakultäten zu berechnen, indem wir einfach den eingebauten „Taschenrechner“ von cmd benutzen:

@ECHO OFF
SETLOCAL ENABLEEXTENSIONS ENABLEDELAYEDEX­PANSION
SET TEMP=1
FOR /L %%i IN (1,1,%1) DO SET TEMP=!TEMP! * %%<span class=„re2“>i
SET /A TEMP=%TEMP%
ECHO %TEMP%
ENDLOCAL

Wir basteln uns hier einfach die komplette Berechnung in einer Zeile zusammen und lassen die dann von SET /A auswerten. Nichts großartig aufregendes aber wahrscheinlich schneller als Rekursion.

Batch-Tricks: Werte tauschen

Eine häufig gebrauchte Sache in einigen Algorithmen (besonders Sortieren) ist, zwei Werte miteinander zu vertauschen. Häufig sieht das dann in etwa so aus:
temp := a;
a := b;
b := temp;

In einigen Sprachen (wie beispielsweise Python oder Lua) kann man das wesentlich eleganter lösen, da es dort Tupel gibt:

a, b = b, a

Und tatsächlich ist eine ähnliche Methode auch in Batchdateien möglich.

Da Variablenersetzung in CMD in dem Moment geschieht, in dem eine Zeile gelesen wird (aus Kompatibilitätsgründen) außer, wenn man ! statt % verwendet (verzögerte Auswertung), kann man sich das zunutze machen, da dann in jedem Falle der Wert der Variablen eingesetzt wurde, bevor die Zeile ausgeführt wurde. Damit kann man nun auch Variablenwerte in einer Zeile tauschen, ohne eine temporäre Variable zu benötigen:

set A=%B%&set B=%A%

Man sollte vielleicht vor dem & kein Leerzeichen machen, wenn es darauf ankommt, daß die Werte tatsächlich nicht mit einem Leerzeichen enden (bei Zahlen, mit denen man rechnet, sollte dies aber egal sein).

Würde man hier ! statt % benutzen, würden beide Variablen auf den Inhalt von B gesetzt werden, da der alte Wert von A nicht mehr erhalten bleibt.

Batch-Tricks: Bibliotheken

Man kann mit call andere Batchdateien aufrufen und ihnen auch Parameter übergeben. Dieser Aufruf funktioniert wie ein Unterprogramm. Gegeben nun den Fall, man hat einen Haufen nützlicher Funktionen, möchte aber nicht immer eine eigene Datei für jede von ihnen oder will nicht immer den Inhalt als Unterprogramm in andere Batchdateien einfügen.

Mit einem relativ einfachen Muster kann man hier alle Funktionen in eine große Batchdatei werfen und trotzdem noch einzeln aufrufen. Ich habe hier im Folgenden mal so etwas vorbereitet, zusammen mit ein wenig zusätzlichem Kram, der vielleicht in so einem Szenarium nützlich sein könnte:
rem Das Sprungziel merken, damit es shift nicht zerstört
set target=%1
rem Sprungziel wegwerfen, damit Unterprogramme Zugriff auf %1, … haben
shift
rem menschenfreundliche Parameterliste basteln
call :get_param_list %
goto %target%
:bla
echo Bla wurde mit folgenden Parametern aufgerufen: %PARAMS%
goto :EOF
:blubb
echo Blubb wurde mit folgenden Parametern aufgerufen: %PARAMS%
goto :EOF
:get_param_list
rem %
enthält noch alle Parameter, daher wieder den ersten wegwerfen
shift
rem Wir brauchen hier verzögerte Auswertung
setlocal enableextensions enabledelayedex­pansion
:get_param_lis­t_loop
set PARAMS=%PARAM­S%, %1
shift
if not%1“=="" goto get_param_list_loop
rem das erste überflüssige Komma wegwerfen
set PARAMS=%PARAM­S:~2%
endlocal & set PARAMS=%PARAMS%
goto :EOF

Der Code besteht hier im Wesentlichen aus ein wenig Initialisierung, namentlich dem set target=%1 und shift, um das Sprungziel zu erhalten und für folgenden Code alles normal aussehen zu lassen. Das Unterprogramm get_param_list ist eigentlich nur dazu da, daß man ein wenig komischen Code zum Anschauen hat sowie ein bißchen Debug-Code fürs Testen. Der Rest sind nur Labels, die die Unterprogramme markieren, die wir sammeln wollen, jedes wie üblich mit goto :EOF abgeschlossen. Die Batch selbst ist also nichts weiter als eine Art switch-Anweisung, die die Funktion auswählt, die man ihr mit dem ersten Parameter übergibt.

Eine einzelne Funktion aus dieser Batch aufzurufen, ist also im Grunde recht einfach (angenommen, wir haben die Batch lib.cmd genannt):
call lib blubb param1 param2 param3 …
was folgende Ausgabe erzeugt:
Blubb wurde mit folgenden Parametern aufgerufen: param1, param2, param3, …

Voilà, und schon haben wir eine Art Bibliotheken oder sogar Namespaces. Wenn man will, kann man das ganze immer noch schachteln, das lasse ich dann als Übung für den geneigten Leser.

Batch-Tricks: Variablen aus Unterprogrammen herausreichen

Unterprogramme in Batchdateien sind relativ einfach zu bewerkstelligen. Einfach ein Sprungmarke schreiben, ein goto :EOF und ein call :label. Nutzt das Unterprogramm jedoch ein setlocal-endlocal-Block, möchte man vielleicht Variablenänderungen des Unterprogramms in den darüberliegenden Gültigkeitsbereich weiterreichen, allerdings setlocal beibehalten, falls man exzessiv lokale Variablen nutzt.

Man kann hier ausnutzen, daß CMD Variablen beim Lesen der Zeile expandiert und nicht erst bei der Ausführung:
setlocal
rem Kram, viel
endlocal & set AEUSSERE_VARI­ABLE=%INNERE_VA­RIABLE%
Der Trick hier ist, daß die Variablenersetzung stattfindet, bevor endlocal ausgeführt wird. Das set danach hingegen wird nach endlocal ausgeführt und beeinflußt demzufolge schon wieder den äußeren Gültigkeitsbereich aber die Variable ist schon ersetzt worden und folglich können wir hierüber den Gültigkeitsbereich überschreiten.

Anmerkung: Ich habe diesen Trick bei Paul Sadowski schon gesehen und befand ihn des Merkens wert. Bislang habe ich es allerdings noch nicht allzu häufig benötigt. Meine geplante Bignum-Implementierung wird davon allerdings ausnehmend Gebrauch machen.

Batch-Tricks: Text umdrehen

Wenn es etwas gibt, was die Windows-Kommandozeile (cmd.exe) ganz gut kann (außer das Starten anderer Programme), dann ist das Verarbeiten von Zeichenfolgen. Nicht gerade auf Perls Niveau, aber sicher schon angenehmer zu nutzen als die C-Standardbibliothek (Reguläre Ausdrücke lassen wir hier mal weg).

Ich spielte nur ein wenig herum und heraus kam folgendes:
@echo off
setlocal enableextensions enabledelayedex­pansion
set „DATA=%*
:loop
set REVDATA=%REVDA­TA%%DATA:~-1%
set DATA=%DATA:~0,–1%
if defined DATA goto loop
echo %REVDATA%
endlocal
Relativ einfach, wie üblich. Das obligatorische setlocal mit den üblichen Optionen (ich setze das eigentlich schon aus Gewohnheit fast immer, egal ob ich es brauche oder nicht). Alle Kommandozeile­nargumente werden in einer Variablen gespeichert und dann Zeichen für Zeichen auseinanderge­kommen. Sobald der Orignaltext leer ist, können wir aufhören und das Resultat ausgeben.

Und es funktioniert sogar mit Unicode:

T:\>reverse ↔¾Ω∞()‡‼ αβγδ да ◙
◙ ад δγβα ‼‡)(∞Ω¾↔
Syndicate content