Die Bash begleitet Linux seit seinen Kindertagen. Trotz ihres biblischen Alters und ihres hohen Reifegrads feilen die Entwickler immer noch an der Shell. Kürzlich veröffentlichten sie Version 4.0. Das Linux-Magazin schaut sich an, ob ein Umstieg lohnt.
Die Bash [1] ist trotz leistungsstarker Konkurrenz, etwa durch die Zsh [2], der Platzhirsch auf der Linux-Konsole. Aber nicht nur interaktiv, sondern auch als überall verfügbare Skriptsprache setzen Anwender sie gerne ein. Die Bash gehört also zum Rückgrat eines funktionierenden Linux-Systems, ein Upgrade mit seinen Vor- und Nachteilen will deshalb gut überlegt sein.
Schon durchschnittlich komplexe Programmpakete geben sich sehr sperrig, wenn es um einen Wechsel auf eine höhere Hauptversion geht. Neben dem eigentlichen Programmpaket muss der Admin oft noch ganze Sammlungen von Bibliotheken aktualisieren. Das schlanke Paket der neuen Bash hängt glücklicherweise nur von Version 6 der Readline-Bibliothek ab. Unter Open Suse ist die Installation besonders einfach, Systemverwalter ersetzen nur die vorhandenen Pakete »bash« und »bash-doc« durch die neue Version und installieren parallel zur »libreadline5« die »libreadline6«. Passende Pakete gibt es im Build Service [3]. Debian bietet ein Paket im Experimental-Zweig an [4]. Anwender anderer Distributionen warten noch etwas oder bauen sich das Paket aus den Quellen selbst.
Kompatibilität
Erster Anlaufpunkt, um die Kompatibilität zu prüfen, ist die Datei »COMPAT« im Dokumentationsverzeichnis. Sie listet acht Punkte auf, die allerdings nicht grundlegender Natur, sondern hauptsächlich der Posix-Kompatibilität geschuldet sind. Ob dies im Einzelnen zu Problemen führt, muss jeder Anwender selbst testen.
Ein weiterer wichtiger Test für die Kompatibilität ist das Init-System mit seinen vielen Start- und Stop-Skripten. Hier gibt es Probleme in Open Suses Skript »/etc/init.d/network«. Die Funktion »check_firewall()« gibt negative Werte zurück, was aus zwei Gründen unsinnig ist: Einerseits sollte der Wertebereich von Returncodes zwischen 0 und 255 liegen, andererseits wertet das Skript den genauen Wert der Rückgabe sowieso nicht aus. Ein Fehler, der also auf Seiten von Open Suse liegt.
Der Parser der Bash 3.2 nimmt das nicht so genau. Unter der Bash 4 jedoch führen die negativen Returncodes zu Fehlern. Als Folge fährt Open Suse die Firewall immer hoch, auch wenn die Systemkonfiguration anderes vorsieht. Solch ein Fehler könnte gar zu Problemen auf anderen Systemen führen, die der Admin gar nicht verändert hat.
Insofern gilt es bei produktiven Systemen, ein Upgrade wohl zu überlegen. Die großen Distributionen werden die neue Version über ihre Community-Distributionen unter die Leute bringen. Bleibt zu hoffen, dass die Anwender solche beschriebenen Fehler schnell finden und beheben.
Auf Entwicklersystemen sieht die Sache anders aus: Hier ist das Produktionsrisiko kleiner. Programmierer möchten das eine oder andere Goodie so schnell wie möglich mitnehmen, etwa die neuen Möglichkeiten der Kommandozeile. Eine Auswahl der Neuerungen zeigt Tabelle 1, eine komplette Aufzählung steht in der Datei »NEWS« der Dokumentation.
|
Tabelle 1: Auswahl |
|
|---|---|
|
Änderung |
Beschreibung |
|
Substrings |
Erweiterung für Positionsparameter unterstützt den |
|
Prozess-ID |
Aktuelle PID der Shell steht in »BASHPID« |
|
»autocd« |
Erlaubt die schnelle Navigation per Eingabe von |
|
»checkjobs« |
Prüft und berichtet über aktive oder angehaltene Jobs |
|
»read« |
Weist auch teilweise erhaltenen Input den Variablen zu und |
|
»mapfile« |
Vereinfacht das Verarbeiten von Dateien |
|
»command_not_found_handle« |
Bash 4 ruft diese Shellfunktion auf, wenn sie ein Kommando |
|
»globstar« |
Sucht mit »**« rekursiv über |
|
Brace |
Erweiterung erzeugt Wertelisten mit führenden Nullen |
|
Standardfehlerkanal |
Statt »2>&1 1>Datei« erlaubt Bash 4 |
|
Standardfehlerkanal |
Analog ersetzt »|&« jetzt »2>&1 |
|
»case« |
Für das Ende von Alternativen erlaubt Bash 4 außer |
|
Variablen-Expansion |
Operatoren »^« und »,« ändern die |
|
Automatische Wandlung |
»declare -u« sowie »declare -l« sorgen |
|
Assoziative Arrays |
Indices dürfen Strings sein: |
|
»coproc« |
Erzeugt asynchrone Prozesse |
|
Select |
Der Aufruf »read -t 0 -u fd« prüft, ob |
Auf der Kommandozeile
Wer regelmäßig die Textkonsole nutzt, wird sich über einige unscheinbare, aber umso nützlichere Erweiterungen freuen: So expandiert die Zeichenfolge »**« zu einer Liste von Dateien und den Pfaden dorthin ab dem aktuellen Arbeitsverzeichnis, also ähnlich dem externen Kommando »find«. Allerdings muss der Anwender vorher mittels »shopt -s globstar« das Feature aktivieren. Eines der größten Mysterien der Bourne Shell, nämlich die Ausgabeumleitung der Standardfehlermeldungen, lösen die Entwickler nun etwas anwenderfreundlicher: Statt des Mantra »2>&1 1>Datei« dürfen Anwender nun »&>> Datei« verwenden, um sowohl die Fehler wie die normale Ausgabe in eine Datei zu lenken. Noch praktischer ist das analoge Kürzel »|&«, das in Pipes gilt.
Assoziative Arrays
Im Gegensatz zum allgemeinen Vorurteil eignet sich die Bash nicht nur für kleinere Wrapper, die das Starten von Programmen vereinfachen. Ein Mythos lautet zum Beispiel, dass Bash-Skripte zu viele Prozesse erzeugen und deswegen nur mäßig performant sind. Doch viele einfache Anwendungen von »sed«, »grep«, »basename« oder »dirname« sind schon seit Langem nicht mehr notwendig – die Bash erledigt solche Aufgaben mit internen Mitteln so performant wie andere Skriptsprachen auch.
Trotzdem werfen Bash-Programmierer immer neidvolle Blicke auf Perl und Python, denn dort gibt es vielfältigere Datenstrukturen. Mit der Version 4 hat die Bash nun endlich zu den eindimensionalen auch assoziative Arrays bekommen. Das allein ist schon ein Grund, auf die neue Version umzusteigen, denn damit lassen sich viele Probleme eleganter lösen. In assoziativen Arrays dürfen Entwickler als Index nicht nur einen Zahlenwert, sondern einen beliebigen String verwenden. Ein Beispiel zeigt Listing 1.
Listing 2 präsentiert einen realeren Anwendungsfall. Ein Skript sortiert Dateien aufgrund von Eigenschaften, zum Beispiel anhand des Erstelldatums, in Verzeichnisse ein. Anschließend soll es jedes Verzeichnis weiterverarbeiten. Das Skript hat aber zwei grundsätzliche Probleme: Einmal funktioniert es nicht mit Verzeichnissen, die Leerzeichen enthalten, zum zweiten verarbeitet es eventuell einzelne Verzeichnisse mehrfach.
Es gibt hier verschiedene Lösungsansätze. Unter Bash 3.2 legt der Programmierer die Verzeichnisse etwa gequotet in einem String oder in einem Array ab. Doppelte Verarbeitung verhindert er im ersten Fall durch eine Suche im String und im zweiten Fall durch eine langsame lineare Suche im Array. Beide Ansätze sind nicht wirklich elegant.
Die Bash 4 erledigt das viel einfacher (siehe Listing 3). Der Verzeichnisname dient als Schlüssel, der eigentliche Wert interessiert nicht. Ab Zeile 12 iteriert die Schleife über alle Schlüssel, das spezielle Konstrukt mit dem Klammeraffen in doppelten Hochkommta sorgt wie überall in der Bash dafür, dass die Shell alle Werte als einzelne Tokens weiterverarbeitet – die Lösung funktioniert also auch mit Leerzeichen in den Zielverzeichnissen.
|
Listing 1: Programmieren mit |
|---|
01 #!/bin/bash
02
03 declare -A name
04
05 name["Linus"]="Torvalds"
06 name["Bill"]="Gates"
07 name["Steve"]="Jobs"
08 name["George W."]="Bush"
09
10 # alle Werte ausgeben:
11 echo "Werte: ${name[@]}"
12
13 # alle Schlüssel ausgeben:
14 echo "Schlüssel: ${!name[@]}"
15
16 # Zugriff auf einzelne Werte
17 for v in "${!name[@]}"; do
18 echo "$v ${name[$v]}"
19 done
|
|
Listing 2: Strings in Listen |
|---|
01 #!/bin/bash 02 03 dirs="" 04 05 for f in "$@"; do 06 d=`getDir "$f"` 07 mkdir -p "$d" 08 mv -f "$f" "$d" 09 dirs="$dirs $d" 10 done 11 12 for d in $dirs; do 13 createIndex "$d" 14 done |
|
Listing 3: Assoziative |
|---|
01 #!/bin/bash
02
03 declare -A dirs
04
05 for f in "$@"; do
06 d=`getDir "$f"`
07 mkdir -p "$d"
08 mv -f "$f" "$d"
09 dirs[$d]=1
10 done
11
12 for d in "${!dirs[@]}"; do
13 createIndex "$d"
14 done
|
Dateiverarbeitung
Ein typisches Programmiermuster enthält Listing 4. Die While-Schleife in den Zeilen 5 bis 8 liest der Reihe nach die Zeilen aus einer Eingabedatei und speichert sie in einem Array. Das Konstrukt kommt immer wieder vor, doch ist es leider fehlerhaft. Endet die letzte Zeile nicht mit einem Zeilenvorschub, also einem Newline-Zeichen, speichert die Schleife die Zeile nicht. Das eingebaute Kommando »read« führt nämlich erst nach abgeschlossener Zeile die Zuweisung aus. Die neue Bash-Version erspart nicht nur Tipparbeit, sondern erzielt auch eine saubere Implementation. Statt der While-Schleife reicht eine einzige Zeile (siehe Zeile 4 in Listing 5). Der Befehl »mapfile« hat zusätzlich als Alias »readarray«, was diesen Zweck noch besser beschreibt.
Der »mapfile«-Befehl ist noch viel leistungsfähiger. In der neuen Manpage oder einfacher mit »help mapfile« auf der Kommandozeile bekommt der neugierige Anwender alle notwendigen Informationen. Der Befehl kann nicht nur mehrere Zeilen am Stück einlesen, sondern diese Zeilen auch häppchenweise über eine Callback-Funktion weiterverarbeiten.
Leider haben die Entwickler die Funktion unglücklich implementiert. Bash 4 ruft den Callback vor dem Einlesen auf und nicht danach. Damit erhält das Skript einen Rückruf, bevor die Shell etwas gelesen hat, verliert aber einen nach der letzten Zeile. Dennoch ist die Funktion nützlich, um ganze Dateien einzulesen.
|
Listing 4: Klassisch Dateien in |
|---|
01 #!/bin/bash 02 03 inputFile="$1" 04 i=0 05 while read line; do 06 lines[$i]="$line" 07 let i++ 08 done < "$inputFile" 09 10 # Weiterverarbeitung des Arrays lines ... |
|
Listing 5: Bash 4 liest Dateien |
|---|
01 #!/bin/bash 02 03 inputFile="$1" 04 mapfile -n 0 lines < "$inputFile" 05 06 # Weiterverarbeitung des Arrays lines ... |
Groß- und Kleinschreibung
Wer schon einmal Konfigurationsdateien oder Benutzereingaben verarbeitet hat und auf robuste Programmierung achtet, kennt das Problem: Lautet der Wert aus der Konfigurationsdatei oder das Ergebnis von »read« nun »JA«, »ja«, »Ja«, »true«, »TRUE« oder etwa »True«?
Bis einschließlich Bash 3.2 formte der Skriptschreiber per »tr«-Aufruf den Wert in eine eindeutige Groß- oder Kleinschreibung um. Die Bash 4 macht hier vieles einfacher. Die Anweisung »declare -u Varname« sorgt automatisch dafür, alle Zuweisungen an die Variable Varname in Großbuchstaben umzuwandeln. Analog dazu gibt es ein »declare -l«, das in Kleinbuchstaben umwandelt. Damit spart der Anwender den Aufruf eines externen Programms und erhöht die Leistung seines Skripts. Wer nur im Einzelfall und nicht global umwandeln will, nutzt die neue Parametererweiterung:
foo=ja
echo ${foo^}
echo ${foo^^}
Der erste Befehl wandelt den ersten Buchstaben in Großbuchstaben um, gibt also »Ja« aus. Der zweite Befehl wandelt dagegen alle Buchstaben um und erzielt das Ergebnis »JA«. Benötigt der Entwickler das Ergebnis in Kleinbuchstaben, verwendet er analog dazu die Schreibweise:
foo=JA
echo ${foo,}
echo ${foo,,}
Noch mächtigere Ersetzungen erlaubt die komplette Syntax »${Var^Pattern}«, die es auch mit den Operatoren »^^«, »,« und »,,« gibt: Nur jene Buchstaben, die auf das Pattern passen, ersetzt dann der Interpreter.
Koprozesse und Wertelisten
Hersteller liefern heute die meisten neuen Rechner mit Multicore-CPUs aus. Nach und nach ziehen deshalb Techniken ein, diese CPUs auch auszulasten. Die Bash 4 bietet die Möglichkeit, so genannte Koprozesse zu starten:
coproc pipes Kommando
Das gibt in den Variablen »pipes[0]« und »pipes[1]« die Deskriptoren für Standard-Ein- und -Ausgabe von Kommando zurück. Über sie kommuniziert der Bash-Hauptprozess mit dem Koprozess – nützlich besonders bei Shellskripten für den parallelen Betrieb [5].
Ein schon vorhandenes, aber ziemlich unbekanntes Bash-Feature sind automatische Wertelisten, die so genannte Brace-Expansion: Der Aufruf »echo {5..15}« zählt von der ersten zur letzten Zahl. Viele Bash-Programmierer verwenden dazu noch einen Aufruf von »seq«:
echo $(seq -s " " 5 15)
Das ist nicht nur weniger performant, sondern auch schwerer zu lesen. Will der Skriptschreiber aber eine solche Ausgabe sortieren, muss er mit dem Nachteil kämpfen, dass sie keine führenden Nullen hat. Mit der Bash 4 schreibt der Shell-User jetzt
echo {05..15}
und erhält – falls nötig – alle Werte zweistellig und mit führenden Nullen.
Features und Kompatibilität
Die neue Ausgabe der Linux-Standardshell wartet mit einer Reihe nützlicher Helfer für Programmierer und Kommandozeilenfans auf. Trotz kleiner Inkompatibilitäten macht der Bash-Maintainer Mut zum Upgrade (siehe Kasten “Interview mit Bash-4-Entwickler Chet Ramey”). Schlank und rank ist die Bash in der neuen Version aber nicht gerade: Das Binary misst jetzt 730 KByte gegenüber 590 KByte in der Vorversion. Sie ist auch messbar – aber kaum fühlbar – langsamer, doch das spielt in der Praxis auf aktuellen Rechnern kaum eine Rolle.
Wer schon jetzt die neuen Funktionen nutzen will, sollte die Kontrolle über seine Zielumgebung haben, um nicht gleich die ganze Bash-Umgebung austauschen zu müssen. In eigenen Skripten sollte er zur Sicherheit zumindest die Umgebungsvariablen »BASH_VERSION« oder »BASH_VERSINFO« abfragen und es mit einer sauberen Fehlermeldung enden lassen, falls die Voraussetzungen für die Bash 4 nicht gegeben sind. (mg)
|
Interview mit Bash-4-Entwickler |
|---|
|
Chet Ramey ist ein leitender Mitarbeiter des Campus-Netzwerks an der Case Western Reserve University in Cleveland, Ohio. Seit 1990 ist er der Maintainer der Bash. Linux-Magazin:Die Version 3.2 der Bash gilt als stabil, wieso also nun Version 4? ChetRamey:Mich freut, dass viele so von der Bash 3.2 denken. Ich veröffentliche nicht häufig Versionen, weil viele Distributionen davon abhängen. Aber es war an der Zeit, einige neue Features und Bugfixes herauszugeben. Linux-Magazin:Welche Features gefallen Dir am besten in der neuen Version? ChetRamey:Wir konnten endlich ein fehlendes Puzzlestück zur Posix-Compliance liefern: Es ist nun nicht mehr nötig, gleich viele öffnende und schließende Klammern in einer »$(…)«-Substitution zu haben. Gerade bei Case-Statements ist das nicht der Fall. Das war nicht einfach mit dem Yacc hinzubekommen. Wer mit »bind -x« eine Tastensequenz an eine Shellfunktion bindet, hat nun Zugriff auf den aktuellen Readline-Buffer und die Cursorposition darin. So kann etwa ein externes Programm die aktuelle Kommandozeile umstellen. Das ist noch nicht sehr bekannt, aber ich sehe da viele Möglichkeiten. Nicht zuletzt sind natürlich noch die assoziativen Arrays zu nennen. Linux-Magazin:Sie sind Teil einer Reihe von neuen Programmierfunktionen von Bash 4. Will die Shell damit zu Skriptsprachen wie Perl oder Python aufschließen? ChetRamey:Die beiden haben einen reicheren Schatz an eingebauten Funktionen. Shells hingegen sollten externe Anwendungen bequem zusammenfügen. Trotzdem lassen sich so komplexe Programme schreiben, der Bash-Debugger ist ein Beispiel dafür. Linux-Magazin:Neu ist eine Reihe von Hilfen für Nutzer der Kommandozeile. Kann die Bash da mit Spezialshells wie der Zsh mithalten? ChetRamey:Das denke ich schon. Wir haben nicht ganz so viele eingebaute Funktionen wie die Zsh, aber es gibt genügend Tools, um die Bash zu einer ebenso umfassenden Arbeitsumgebung zu machen. Linux-Magazin:Viele Admins diskutieren darüber, welche Shell-Eigenschaften portable Skripte nutzen sollten. Einige nutzen ausgiebig Bash-Eigenheiten, andere plädieren für klassische Bourne Shell. ChetRamey:Es gibt nicht wirklich eine Bourne-Shell-Kompatibilität, denn es gibt sie als Version aus Unix v7, SVR2, SVR3, SVR4, SVR4.2 und dazu Mischformen verschiedener Hersteller. Wer wirklich an portablen Skripten interessiert ist, sollte sich an den Posix-Standard halten. Linux-Magazin:Wie adressiert die Bash 4 Kompatibilitätsfragen? Empfiehlst Du ein unmittelbares Upgrade oder einen Parallelbetrieb beider Versionen? ChetRamey:Bash 4 ist so abwärtskompatibel wie nur möglich, aber an einigen Stellen war das Verhalten von Version 3.2 einfach fehlerhaft. Da haben wir dann etwas Kompatibilität geopfert. Ansonsten gibt\’s noch die Optionen »compat31« und »compat32«, die einige alte Verhaltensweisen bewahren. Ich halte das Maß an Kompatibilität für hoch genug, sodass Anwender direkt auf Bash 4 aufrüsten und sich dann schrittweise mit den neuen Features vertraut machen können. |
|
Infos |
|---|
|
[1] Bash: [http://tiswww.case.edu/php/chet/bash/bashtop.html] [2] Zsh: [http://www.zsh.org] [3] Bash 4 im Open Suse Build Service: [http://software.opensuse.org/search/search?baseproject=ALL&q=bash] [4] Bash 4 im Experimental-Repository von Debian: [http://packages.debian.org/experimental/bash-builtins] [5] Bernhard Bablok, “Parallelarbeit: Bash-Skripte, die Multicore-Prozessoren ausreizen”: Linux-Magazin 02/09, S. 70 |
|
Der Autor |
|---|
|
Bernhard Bablok betreut bei der Allianz Shared Infrastructure Services ein Datawarehouse mit technischen Performance-Messdaten von Mainframes bis zu Servern. Wenn er nicht Musik hört, mit dem Rad oder zu Fuß unterwegs ist, beschäftigt er sich mit Linux oder Objektorientierung. |





