Batsh schlägt zwei Fliegen mit einer Klappe: In dieser Sprache geschriebene Programme lassen sich sowohl in Bash-Skripte als auch in die unter Windows genutzten Batch-Dateien übersetzen.
Zum Automatisieren wiederkehrender Aufgaben setzen Linux-Anwender gerne Bash-Skripte ein. In der Windows-Welt kommen analog sogenannte Batch-Dateien (Endung: .bat) zum Einsatz. Die in Linux- und Windows-Skripten verwendeten Befehle unterscheiden sich jedoch teilweise deutlich. Wer sowohl unter Linux als auch Windows arbeitet, muss folglich nicht nur immer zwei separate Skripte erstellen und pflegen, sondern sich auch mit den verschiedenen kryptischen Anweisungen herumschlagen.
Nach dem Willen des Google-Angestellten Carbo Kuo soll das jedoch ein Ende haben: Der in der Schweiz lebende Chinese arbeitet derzeit an einer Programmiersprache namens Batsh, deren Code ein Compiler wahlweise in passende Bash- oder Batch-Skripte übersetzt.
Begrüßungsritual
Um etwa die beliebte Meldung Hallo Welt! auszugeben, genügt in Batsh die folgende Zeile:
println("Hallo Welt!");
Was der Compiler daraus macht, können Sie auf der Internetseite http://batsh.org direkt ausprobieren: Tippen Sie die Zeile in das Textfeld auf der linken Seite und klicken Sie dann auf Compile to Bash. Der Internetdienst übersetzt jetzt den Batsh-Programmcode und zeigt ein gleichwertiges Bash-Skript auf der rechten Seite an. In diesem Fall steht dort die Zeile:
"echo" "-e" "Hallo Welt!"
Eine Batch-Datei für Windows generieren Sie analog über Compile to Windows Batch, das zugehörige Ergebnis für das Hallo-Welt-Beispiel zeigt Abbildung 1. Bei der Eingabe des Programmcodes unterstützt Sie das Textfeld auf der linken Seite mit verschiedenen Funktionen. So färbt es unter anderem den Programmcode ein und ergänzt automatisch schließende Klammern.

Examples lassen sich ein paar Beispielskripte abrufen, ein Klick auf About Batsh führt zu dessen GitHub-Seite.” width=”300″ height=”120″ />
Examples lassen sich ein paar Beispielskripte abrufen, ein Klick auf About Batsh führt zu dessen GitHub-Seite.Wenn Sie mit Batsh arbeiten möchten, sollten Sie zumindest schon einmal selbst ein kleines Bash-Skript geschrieben oder bereits in einer anderen Sprache programmiert haben. Dies erleichtert den Einstieg, zumal die Dokumentation nur aus einer extrem kurzen und zudem noch unvollständigen Referenz besteht [1].
Schnellmerker
Batsh orientiert sich ein wenig an der Programmiersprache C, lässt Ihnen aber ähnliche Freiheiten wie eine Skriptsprache. Listing 1 zeigt ein einfaches Beispielskript. Es bastelt den Dateinamen foto1.tiff aus Einzelteilen zusammen.
Listing 1
nummer = 1; praefix = "foto"; dateiname = praefix ++ nummer ++ ".tiff"; // Dies ist ein Kommentar println(dateiname);
Zunächst speichert die Variable nummer die Zahl 1. Jede Anweisung endet wie in C mit einem Semikolon. Die zweite Zeile verstaut analog den Text foto in der Variablen praefix. Zeichenketten müssen stets in normalen Anführungszeichen stehen, einfache Striche ' sind in Batsh nicht erlaubt.
Das ++ in der dritten Zeile hängt zwei Zeichenketten hintereinander. In der Variablen dateiname liegt somit der Dateiname foto1.tiff. Diesen gibt schließlich die letzte Zeile aus. Kommentare leiten Sie wie in C oder Java mit zwei Schrägstrichen ein. Das übersetzte Bash-Skript sehen Sie in Abbildung 2.
Häufig benötigte Befehlssequenzen lassen sich in einer Funktion kapseln. Ein einfaches Exemplar zeigt Abbildung 3. Die Notation dürfte Programmierern von Bash-Skripten bekannt vorkommen: Nach dem Schlüsselwort function folgt der Name der Funktion. Alle Anweisungen, die die Funktion später ausführen soll, stehen zwischen den geschweiften Klammern. In Abbildung 3 gibt die Funktion dateiname lediglich einen Text aus. Die letzte Zeile ruft schließlich die Funktion auf, und somit die in ihr enthaltenen Anweisungen.
Vorne rein, hinten raus
Über die runden Klammern lassen sich Parameter an die Funktion übergeben. Wie das funktioniert, verdeutlicht Listing 2: Beim Aufruf der Funktion dateiname landet die Zahl 1 in der Variablen num, der Text .png in der Variablen ende. Diese beiden Variablen stehen dann in der Funktion zur Verfügung.
Listing 2
function dateiname(num, ende) {
datei = "foto" ++ num ++ ende;
return datei;
}
dn = dateiname(1, ".png");
println(dn);
Anschließend baut die Funktion dateiname mit ihnen einen Dateinamen zusammen, den sie jedoch diesmal nicht mit println() auf den Bildschirm schreibt, sondern zurückliefert. Den ausgespuckten Dateinamen fängt dann wiederum die vorletzte Zeile in der Variablen dn auf. Welchen Wert die Funktion auf diese Weise zurückgibt, zeigt das Schlüsselwort return an, das auch gleichzeitig die Ausführung der Funktion abbricht.
Fallstricke
Im Gegensatz zu anderen Programmiersprachen erfolgt in Batsh keine Typprüfung. Sie könnten der Funktion dateiname daher auch zwei unsinnige Werte übergeben:
dn = dateiname("hmpf", 9876);
Listing 2 gäbe dann den Dateinamen fotohmpf9876 aus – in diesem Fall relativ harmlos. In komplexeren Funktionen können aber falsche oder mit Tippfehlern versehene Parameter mitunter drastische Auswirkungen haben.
Definieren Sie in einer Funktion eine neue Variable, dann ist diese nur in der Funktion sichtbar. Am Ende von Listing 3 können Sie folglich nicht auf die Variable b zugreifen. Wenn Sie es wie gezeigt dennoch probieren, vermutet Batsh, dass Sie eine neue Variable b meinen. Da neue Variablen jedoch standardmäßig leer bleiben, liefert println(b) einfach nur eine leere Zeile.
Listing 3
function gutentag() {
b = "Tag!";
}
gutentag();
// b ist nur in der Funktion sichtbar
println(b);
In Listing 4 versucht die Funktion gruss() die Variable a zu ändern. Rufen Sie das fertige Skript auf, dann erhalten Sie die Ausgabe Hallo: Zwar kann gruss() auf den Wert a zugreifen, diesen aber nicht verändern. Versuchen Sie es doch, wie im Beispiel mit a = "Welt";, erstellt Batsh eine zweite Variable namens a, die nur innerhalb der Funktion sichtbar ist. Um das zu verhindern, müssen Sie die Variable zunächst mit einem global in der Funktion verfügbar machen. Das entsprechend veränderte Listing 5 gibt damit dann am Ende den Wert Welt aus.
Listing 4
a = "Hallo";
function gruss() {
a = "Welt";
}
gruss();
println(a);
Listing 5
a = "Hallo";
function gruss() {
global a;
a = "Welt";
}
gruss();
println(a);
Rundherum
Möchten Sie zehn TIFF-Bilder ins JPEG-Format konvertieren, müssen Sie jede Datei von einem Bildbearbeitungsprogramm umwandeln lassen. Bei dieser Aufgabe hilft die von Batsh bereitgestellte while-Schleife.
Wie Abbildung 4 zeigt, gelingt sie in Batsh kürzer, knackiger und somit auch übersichtlicher als in Bash-Skripten: Solange die Bedingung in den Klammern erfüllt bleibt, führt das Skript alle Anweisungen in den geschweiften Klammern aus. In Abbildung 4 erhöht sich folglich die Zahl in nummer so lange um 1, bis nummer den Wert 11 enthält.
Mithilfe dieser Schleife lassen sich jetzt die Fotos konvertieren. Den passenden Programmcode zeigt Listing 6: Die while-Schleife bastelt zunächst aus der aktuellen Nummer die Dateinamen des originalen und des konvertierten Fotos. Die von Batsh bereitgestellte Funktion call startet ein beliebiges Programm. Als ersten Parameter erwartet sie den Dateinamen des auszuführenden Programms – in Listing 5convert aus dem ImageMagick-Paket. Alle übrigen Parameter übergibt call direkt an convert.
Listing 6
nummer = 1;
praefix = "foto";
while (nummer < 11) {
// Dateinamen erstellen:
original = praefix ++ nummer ++ ".tiff";
konvertiert = praefix ++ nummer ++ ".jpg";
// Konvertieren:
call("convert", original, konvertiert);
// Nummer um 1 erhöhen:
nummer = nummer + 1;
}
Sie dürfen call nicht nur zwei, sondern beliebig viele Werte übergeben – je nachdem, welche und wie viele Parameter das aufgerufene Programm verlangt. Damit Listing 6 sowohl unter Linux als auch Windows funktioniert, muss auf beiden Systemen convert installiert sein. Die in Bash-Skripten häufig genutzte for-Schleife kennt Batsh übrigens noch nicht.
Lesezirkel
Listing 6 baut die Dateinamen selbst zusammen. Eleganter wäre es, das Skript alle Dateinamen im Verzeichnis einlesen zu lassen und diese dann nacheinander an convert zu verfüttern. Dafür bietet Batsh die eingebaute Funktion readdir:
alledateien = readdir();
readdir() ruft im Hintergrund ls beziehungsweise unter Windows dir /w auf und liefert einfach die Ergebnisse zurück. Das führt jedoch zu gleich zwei Problemen: Zum einen gilt es, die von ls gelieferten Dateinamen mit Vorsicht zu genießen: Sie könnten beispielsweise Sonderzeichen enthalten, die die Bash später falsch interpretiert.
Zum anderen gibt readdir() einfach eine lange Zeichenkette mit den Dateinamen aus. Batsh bietet derzeit jedoch noch keine Möglichkeit, Zeichenketten in Einzelteile zu zerlegen.
Arrays
Batsh kennt die auch von der Bash her bekannten Arrays. Dabei handelt es sich um Variablen, die eine Liste mit beliebigen Daten speichern:
a = [1, 2, "hallo"];
Die Elemente im Array nummeriert Batsh durch, beginnend bei 0. Um an ein Element in der Liste zu gelangen, müssen Sie dessen Nummer in eckigen Klammern notieren. Die folgende Zeile steckt die 2 aus dem Array a in die Variable b:
b = a[1];
Derzeit gibt es jedoch keine Funktionen, mit denen man das Array um weitere Elemente ergänzen könnte. Lediglich die Anzahl der Elemente lässt sich mit der nicht dokumentierten Funktion len abfragen: laenge = len(a);. Der Nutzen der Arrays hält sich somit in Grenzen.
Existenzfragen
Als wesentlich nützlicher als readdir() erweist sich die Funktion exists(): Sie prüft, ob eine Datei existiert. Ein Beispiel dafür zeigt Listing 7: Dort testet zunächst exists(), ob die Datei foto1.png im Verzeichnis /home/tim/Fotos/ liegt.
Listing 7
if (exists("/home/tim/Fotos/foto1.png")) {
println("foto1.png existiert.");
}
else {
println("foto1.png existiert nicht");
}
Den von exists() zurückgegebenen Wert prüft dann die if-Abfrage: Sofern die Datei vorhanden ist, führt das Skript die Anweisungen zwischen den oberen geschweiften Klammern aus, andernfalls die Anweisungen in den geschweiften Klammern nach dem else.
Den else-Teil können Sie bei Bedarf weglassen. Wie bei while dürfen Sie in den runden Klammern hinter if jeden logischen Ausdruck unterbringen, also etwa auch if (a > b) { ... }.
Allerdings gibt es in Listing 7 ein kleines Problem: Die Pfadangabe /home/tim/Fotos ist auf ein Linux-System zugeschnitten. Der Batsh-Compiler übersetzt sie jedoch in der Batch-Datei nicht in das passende Windows-Pendant, wie Abbildung 5 beweist. Es gibt auch keine Möglichkeit, dies zu ändern; Batsh kann Ihrem Skript noch nicht einmal verraten, ob es unter der Bash oder unter Windows läuft.

/home/tim/Fotos übernimmt der Batsh-Compiler einfach in die fertige Batch-Datei.” width=”300″ height=”106″ />
Abbildung 5: Die Pfadangabe/home/tim/Fotos übernimmt der Batsh-Compiler einfach in die fertige Batch-Datei. Es gibt lediglich die eingebaute Funktion bash(), die einfach den ihr übergebenen Bash-Befehl ausführt:
bash("man convert");
Der Batsh-Compiler übersetzt diese Funktion nur dann, wenn Sie ein Bash-Skript erstellen lassen. Der Aufruf man convert taucht folglich nur im Bash-Skript auf, nicht jedoch in der Windows-Batch-Datei. Analog gibt es die Funktion batch(), die eine übergebene Batch-Anweisung absetzt. Der Batsh-Compiler übersetzt diese Funktion nur dann, wenn Sie das Skript für Windows kompilieren.
Oh, ein Kamel!
Sie können Batsh-Programme nicht nur auf der Batsh-Homepage [2] übersetzen lassen: Carbo Kuo stellt den Compiler im Quellcode unter einer MIT-Lizenz auf GitHub zur Verfügung [1].
Batsh wurde vollständig in der funktionalen Programmiersprache Ocaml geschrieben. Um den Compiler installieren zu können, benötigen Sie folglich eine entsprechende Ocaml-Umgebung, die sich jedoch in den Repositories der meisten Distributionen findet. Am einfachsten gelingt die Installation von Batsh über den Ocaml-eigenen Paketmanager OPAM. Unter Ubuntu richten Sie ihn über das Paket opam ein:
$ sudo apt-get install opam m4
Zur Installation einiger von Batsh verwendeter Zusatzbibliotheken benötigen Sie das Paket m4. Sobald diese Voraussetzungen erfüllt sind, spielt der folgende Befehl Batsh ein:
$ opam install batsh
So verspricht es zumindest Carbo Kuo. Auf unserem Testrechner mit Ubuntu 14.10 ließen sich jedoch die Abhängigkeiten von Batsh nicht auflösen.
Aus dem gleichen Grund scheiterte im Test auch das manuelle Übersetzen von Batsh aus dem Quellcode. Dazu sollte es eigentlich ausreichen, Ocaml über den Paketmanager einzurichten, das aktuelle Batsh-Quellcodepaket [3] herunterzuladen, es zu entpacken und dann mittels make und make install zu übersetzen und zu installieren.
Ausgedruckt
Die Batsh-Funktion println() besitzt noch den Bruder print(). Auch er präsentiert den übergebenen Text auf der Kommandozeile, schreibt ihn aber nicht in eine eigene Zeile. Beide Funktionen erlauben beliebig viele Parameter. Das folgende Beispiel gibt also den Text Hallo Welt aus:
a="Welt";
print("Hallo ", a);
Alternativ zu print() können Sie auch das nicht dokumentierte echo() verwenden. print() übersetzt der Batsh-Compiler zu "echo" "-ne", die Funktion echo() hingegen einfach nur zu "echo".
Fazit
Man merkt Batsh deutlich sein frühes Entwicklungsstadium an. Die Dokumentation gibt sich wortkarg, Programmierer stoßen recht schnell an die Grenzen der Skriptsprache. Nachdem die ersten Versionen von Batsh im Monatsrhythmus erschienen, hat sich die Entwicklung mittlerweile deutlich verlangsamt. Die letzte Version 0.6.0 erschien im September 2014, knapp ein Jahr nach dem Vorgänger. Obendrein ersetzt unter Windows derzeit die Powershell langsam aber sicher die alten Batch-Skripte.
In seinem jetzigen Zustand kann Batsh immerhin Grundgerüste für Bash-Skripte und Batch-Dateien generieren. Insbesondere While-Schleifen und If-Abfragen fallen in Batsh wesentlich kürzer und übersichtlicher aus. Der Ansatz von Batsh bleibt zudem bestechend: Sie müssen nur noch ein Skript entwickeln und für die Zielplattformen übersetzen lassen. Dort sparen Sie sich zudem die Installation eines fetten Interpreters wie etwa Python oder Ruby. Es bleibt also zu hoffen, dass weitere Entwickler Carbo Kuo etwas unter die Arme greifen und das Projekt vorantreiben.
Infos
[1] Batsh auf Github: https://github.com/BYVoid/Batsh
[2] Batsh im Browser: http://batsh.org/
[3] Batsh-Quellcode: https://github.com/BYVoid/Batsh/releases








