Mit der Version 4 erhält die Bourne Again Shell (Bash) neue Funktionen, die sie einmal mehr als leistungsfähige Programmierschnittstelle zum System und zu Applikationen qualifiziert.
Seit einiger Zeit bringen die Linux-Distributionen die Bourne Again Shell (Bash) in Version 4 mit. Sie löst damit in vielen Fällen die Version 3.2 ab, deren Eigenschaften sie weitgehend übernimmt und erweitert. Seit Januar 2010 gibt es zudem die Version 4.1, die wieder eine ganze Reihe von Neuerungen enthält. Es steht zu erwarten, dass die Distributionen ihre nächsten Releases mit dieser Version ausliefern.
Wer nicht so lange warten möchte, installiert sich die aktuelle Version schnell und einfach aus den Quellen (siehe Kasten “Bash kompilieren und installieren”). Wie immer spielen sich einige der Veränderungen unter der Oberfläche und für den Anwender kaum erkennbar ab. Ein Menge Neuerungen erleichtert Ihnen aber dem Umgang mit der Shell ganz erheblich.
Bash kompilieren und installieren
Das Archiv mit den Quelltexten finden Sie im Netz unter http://tiswww.case.edu/php/chet/bash/bashtop.html. Beim Auspacken entsteht das Verzeichnis bash-4.1 im aktuellen Verzeichnis. Wechseln Sie dorthin und erstellen Sie mittels ./configure ein Makefile. Mit dem Befehl make bauen Sie die Komponenten. Ein make test überprüft das Ergebnis, mit su -c 'make install' installieren Sie die Software unter /usr/local/bin/ auf dem Rechner.
Haben Sie die Suchreihenfolge der Shell so konfiguriert, dass Sie zuerst in /usr/local/bin/ nach Binaries schaut, kommt nun die neue Shell per Default zum Einsatz. Andernfalls rufen Sie diese unter Angabe des vollen Pfades auf.
Neue Shell-Optionen
Shell-Optionen steuern grundlegende Eigenschaften der Bash, wie etwa die Interpretation von Metazeichen, die History-Funktion oder das Verhalten von Hintergrundprozessen. Mit den Befehlen set und shopt aktivieren oder deaktivieren Sie diese Optionen im laufenden Betrieb. Alternativ geben Sie die Optionen wie zum Beispiel bei bash -o noexec als Argumente für den Parameter -o beim Start an. In dieser Form führt die Shell ein als Argument übergebenes Skript nicht aus, sondern untersucht nur dessen Syntax auf grobe Fehler.
Die Bash kennt zwei Varianten von Shell-Optionen: Die klassischen, die mehr oder weniger kompatibel zur Ur-Shell arbeiten, steuern Sie mit set. Die neueren, die die Bash erweitern und oft auch bei der Zsh existieren, beeinflussen Sie über den Befehl shopt.
Das Kommando set erlaubt es, mittels -o Optionen über deren Namen zu setzen. Möchten Sie eine Option auf diese Weise setzen, verwenden Sie set -o Name in einem Skript oder Terminal. Setzen Sie statt des Minuszeichens ein Pluszeichen, dann deaktivieren Sie die entsprechende Option.
Bei den erweiterten Optionen verhält es sich etwas anders: Mit dem Parameter -s setzen Sie eine Option, wohingegen -u die Option deaktiviert. Das Kommando shopt Option zeigt den aktuellen Zustand der Option. Ohne Argumente gibt der Befehl Informationen über alle erweiterten Shell-Optionen aus, mit shopt -p erhalten Sie dagegen eine Ausgabe, die sich gleichzeitig als Eingabe eignet.
Die neue Option autocd ermöglicht einen Verzeichniswechsel ohne die Eingabe eines vorangestellten cd auf der Kommandozeile. Ohne diese Option führt die Eingabe zu Statusmeldung bash: /tmp/: ist ein Verzeichnis., ohne dass etwas passiert. Mit aktivierter Option zeigt die Bash den Befehl cd /tmp/ an und wechselt in das angegebene Verzeichnis. In diesem Zusammenhang lohnt es sich, die Option dirspell zu erwähnen: Ist sie aktiviert, versucht die Shell Schreibfehler in Verzeichnisnamen automatisch zu korrigieren.
Die Option checkjobs hat mit Version 4.0 Einzug in die Bash gehalten. Sobald Sie sie gesetzt haben, prüft die Shell vor dem Beenden, ob es noch aktive oder angehaltene Hintergrundprozesse gibt. Das beugt einem unbeabsichtigtes Beenden solcher Tasks vor.
Für Shell-Programmierer bedeutsam sind drei neue Optionen, die dafür sorgen, dass sich die aktuelle Version der Bash kompatibel zu Vorversionen verhält: compat31 macht die Bash kompatibel zur Version 3.1, compat32 zur Version 3.2 und compat40 regelt entsprechend das Verhalten der Bash 4.1 in Bezug auf die Version 4.0. Die tatsächlichen Auswirkungen dieser Einstellungen fallen in den meisten Fällen gering aus. Die Datei CHANGES und die aktuelle Manpage beziehungsweise FAQ listen Genaueres dazu auf.
Builtins und reservierte Wörter
Wie bei jeder neuen Version erhält die Bash eine Reihe neuer eingebauter Befehle (“builtins”) und ergänzt die Optionen der vorhandenen. Zu den wichtigsten eingebauten Befehlen gehört mapfile, der auf das Synonym readarray hört: Er liest voreingestellt Zeilen von der Standardeingabe oder einem anderen Kanal in ein Array ein (Vorgabe: MAPFILE).
So liest das Kommando aus Listing 1 elf Zeilen (-n 11), beginnend in Zeile 2 (-s 1, das erste Element liegt auf Position 0) aus der angegebenen Datei ein. Alle zwei Zeilen (-c 2) gibt der Befehl die Anzahl eingelesener Datensätze an (-C echo). Es handelt sich bei dem Builtin nicht um ein wirklich neues Feature, alles ließe sich mithilfe von Schleifen nachprogrammieren. Es erleichtert aber die Arbeit.
Listing 1
mapfile -n 11 -s 1 -c 2 -C echo < /etc/passwd 1 3 […] 9
Auch den klassischen Read-Befehl zum Einlesen haben die Entwickler überarbeitet: Die neue Option -i erlaubt es, beim Einsatz der Readline-Bibliothek (-e) Vorgaben für den Reply-Buffer anzugeben (Listing 2).
Listing 2
$ read -t2 -ei readline readline $ echo $REPLY readline
Readline löscht die Vorgaben nicht automatisch, sondern ergänzt sie um eigene Angaben. Um also statt readline die Zeichenkette nixda in die Variable REPLY zu übergeben, müssen Sie die Eingabezeile im obigen Beispiel zunächst mit [Strg]+[U] komplett löschen.
Die Befehle printf und echo -e geben nun die Steuerzeichen \" (maskierte Hochkommata) und \? (maskiertes Fragezeichen) problemlos aus. Bei printf ermöglicht -v die Ausgabe in eine Array-Variable.
Der Multifunktionsbefehl declare definiert Variablen und Funktionen. Mit der neuen Option -p gibt der Befehl alle momentan gesetzten Variablen mit ihren Werten aus (Listing 3). Das neue Format eignet sich als Eingabe, beispielsweise in Skripten. Mit der Option -f gibt declare Funktionen aus (Listing 4). Auch hier eignet sich die Ausgabe – oben der Funktionsname, zwischen den geschweiften Klammern der Code – wieder als Eingabe.
Listing 3
$ declare -p declare – BASH="/usr/local/bin/bash41" […] declare -r bash3="4.1.0(1)-release"
Listing 4
$ declare -f
command_not_found_handle ()
{
if [ -x /usr/lib/command-not-found ]; then
/usr/bin/python /usr/lib/command-not-found – $1;
return $?;
else
return 127;
fi
}
Die weiteren neuen Optionen von declare bewirken ein Umwandeln der Buchstaben eines Strings in Klein- (-l) oder Großbuchstaben (-u) beziehungsweise eine Umwandlung nur des ersten Buchstabens des Strings (-c) in einen Großbuchstaben (Listing 5).
Listing 5
$ declare -l kg=TZ ; echo $kg tz $ declare -u kg=tz ; echo $kg TZ $ declare -c kg=tz ; echo $kg Tz
Das Case-Statement erhielt durch neue Begrenzerzeichen zwei zusätzliche Varianten: ;& bewirkt, dass case auch die folgende Zeile auswertet – normalerweise beendet die Bash die Verzweigung sofort nach einem Treffer. Listing 6 zeigt ein Beispiel: Hier gibt tst z wie erwartet 1z aus, aber tst t erzeugt 1t 1z als Ausgabe. Mit ;;& gehen Sie noch einen Schritt weiter: Dann wertet die Bash alle folgenden Tests aus.
Listing 6
[…]
tst ()
{
case $1 in
t) echo "1t" ;&
z) echo "1z" ;;
esac
}
[…]
Die Befehle zur Jobkontrolle (jobs, kill und wait) verarbeiten mit %Jobnummer nun eine weit verbreitete Schreibweise für diesen Parameter. Ein neues Schlüsselwort coproc erzeugt einen Co-Prozess, ähnlich einem durch & gestarteten Hintergrundprozess. Allerdings bleiben Co-Prozesse im Unterschied zu den normalen Jobs mit dem startenden Terminal verbunden, können also bei Ein- und Ausgaben damit kommunizieren. Die Syntax ist einfach:
coproc: coproc Name command Redirections
Eine Besonderheit liegt darin, dass Sie Co-Prozessen einen Namen geben dürfen. Voreingestellt ist dafür COPROC“. Unter examples/functions/ finden Sie die Datei coproc.bash mit Beispielen zum Einsatz der neuen Funktion.
Verbundene Befehle kennt die Bash in mehreren Formen: Zwischen einfachen runden Klammern stehen so genannte Listen, die die Bash in einer Subshell ausführt. Zwischen einfachen geschweiften Klammern stehende Listen führt die Bash in der aktuellen Shell aus. Setzen Sie doppelte runde oder gar (doppelte) eckige Klammern, interpretiert die Shell den Inhalt als Ausdruck. In einem solchen expandiert die Shell keine Wörter oder Dateinamen, was eine Besonderheit in der Befehlszeile ist.
In Ausdrücken innerhalb von doppelten runden oder doppelten eckigen Klammern bestimmen die durch Operatoren definierten Vergleiche den Rückgabewert des Ausdrucks. Dabei vergleicht die Bash die linke Seite vor dem Operator mit der rechten. Doppelte runde Klammern kommen bei numerischen Vergleichen zum Einsatz, die doppelten eckigen für alle anderen. In der numerischen Variante erzeugen Ergebnisse im Ausdruck ungleich Null einen Rückgabewert von Null:
$ (( 3/5 )) ; echo $? 1
Das Kommando echo $? gibt dabei den Rückgabewert der zuletzt ausgeführten Befehlszeile aus. Zwischen eckigen Klammern stehende Ausdrücke kommen oft in Kontrollstrukturen zum Einsatz. Hier entscheidet der Rückgabewert, wie die Bash weiter verfährt. Das Konstrukt wirkt wie der eingebaute Test-Befehl und unterstützt dessen Optionen. Über den Operator =~ werten Sie erweiterte reguläre Ausdrücke im Stile von Egrep aus. Das geht immer über die (neue) Variable $BASH_REMATCH:
[[ "$line" =~ "abc(.*)xyz" ]] &&
echo "${BASH_REMATCH[0]}"
Die Variable $line enthält die zu testenden Zeilen, zum Beispiel eingelesen in einer Read-Schleife. Die Mustersuche filtert nach Zeichenketten, die mit abc beginnen und auf xyz enden. Findet die Bash eine solche, ist der Ausdruck innerhalb der Klammern wahr und die Shell gibt das Element des Arrays aus, das den Treffer enthält.
Der eingebaute Help-Befehl hat nun zwei neue Optionen: Mit -d zeigt er eine Kurzinformation ähnlich apropos an, mit -m gibt die Bash die vorhandenen Informationen in Form einer Manpage aus. Das funktioniert nicht nur für die Builtins, sondern auch für Variablen und Schlüsselwörter (Listing 7).
Listing 7
$ help -m '{'
NAME
{ … } - Group commands as a unit.
SYNOPSIS
{ COMMANDS ; }
DESCRIPTION
Group commands as a unit.
[…]
Wildcards und Operatoren
Das Metazeichen ** passt rekursiv auf alle Dateien (auch in Unterverzeichnissen), wie sie etwa ls -R ausgibt. Um es zu nutzen, aktivieren Sie die Shell-Option globstar (shopt -s globstar). Folgt auf das Metazeichen ein Slash, steht das Muster nur für (Unter-)Verzeichnisse (Listing 8). Der neue Wildcard hilft dabei, Befehle ohne besondere Musterfähigkeiten so einsetzen, als verfügten sie über die gleichen Features wie die Shell.
Listing 8
$ ls bash-4.0/ /*.o bash-4.0/alias.o […] bash-4.0/lib/readline/undo.o … bash-4.0/xmalloc.o
Fast ebenso wichtig und nützlich sind zwei neue Umleitungsoperatoren: &>> Datei hängt sowohl die Standardausgabe als auch die Standardfehlerausgabe an die angegebene Datei an. Mit |& leiten Sie die Ausgaben in eine Pipe um. Diese speziellen Umleitungen nimmt die Bash erst nach den explizit in der Befehlszeile angegebenen Umleitungen vor.
Neue Variablen
Zum einen erhält die Bash mit Version 4 eine Reihe weiterer (interner) Variablen, die direkt mit der Shell verbunden sind. Zum anderen bietet sie nun assoziative Arrays – also solche mit nicht nummerischem Index. Die neue interne Variable BASHOPTS enthält alle momentan durch shopt aktivierten Optionen; analog dazu liefert SHELLOPTS das für die mit set gesteuerten.
Die Prozess-ID der aktuellen Shell finden Sie über BASHPID heraus. Das hilft besonders in Skripten – etwa wenn es gilt, die aktuelle von mehreren laufenden Shells zu unterscheiden. Diese Variable verhält sich in verschiedenen Situationen anders als $$ (das doppelte Dollarzeichen).
Die neue Variable BASH_SUBSHELL zeigt durch Werte größer Null an, dass es sich bei der aktuellen Shell um eine Subshell handelt. Bei früheren Version galt es, dies über die Variable SHLVL zu bestimmen. Das bedeutete aber, dass Sie diese Variable einmal vor dem Aufruf speichern, dann den aktuellen Wert mit dem gespeicherten vergleichen und dies noch mal auswerten mussten. Die folgenden Variablen sind alle (assoziative) Arrays:
BASH_ALIASESenthält ein Array mit den Alias-Definitionen,BASH_CMDSenthält die im Command-Hash vorgehaltenen externen- Befehle,
BASH_SOURCEentspricht diesem für Skripte.
Im Prompt (repräsentiert durch die Variablen PS1 bis PS4) expandieren \w und \W zum aktuellen Verzeichnis mit Pfad. Da diese Ausgabe unter Umständen sehr lang gerät, gibt es durch die neue Variable PROMPT_DIRTRIM eine Möglichkeit, die Länge der ausgegebenen Zeichen zu beschränken. Sie enthält die Anzahl darzustellender Zeichen. Wächst die Länge darüber hinaus an, markiert die Shell eine Auslassung mit drei Punkten.
Beim Debuggen hilft die Variable BASH_XTRACEFD, in der Sie bei Bedarf die Nummer eines File-Descriptors speichern. Haben Sie die Variable gesetzt, verwendet die Shell den Descriptor für Xtrace-Ausgaben (set -x).
Neu in der Version 4 hinzugekommen sind assoziative Arrays, die Sie mittels declare -A Name erzeugen. Diese unterscheiden sich von den bisherigen Arrays dadurch, dass Sie als Index beliebige Strings benutzen dürfen. Abgesehen davon unterscheidet sich der Einsatz beider Datentypen nicht (Listing 9).
Listing 9
$ declare -A array
$ declare array[b]=B
$ declare array[c]=c
$ declare array[a]=a
$ echo ${array[*]}
a B c
$ echo ${!array[*]}
a b c
Beim Expandieren vom Variablen haben Sie nun die Möglichkeit, die Groß/Kleinschreibung der Inhalte gezielt zu steuern. Ein angehängtes ^^ bewirkt, dass die Bash nur Großbuchstaben ausgibt, auch wenn ein String eigentlich in Kleinbuchstaben vorliegt (Listing 10). Fehlt ein abschließendes Muster (hier das “e”), gilt die Funktion für alle Zeichen. Analog arbeitet das doppelte Komma (,,) in Bezug auf Kleinbuchstaben.
Listing 10
$ echo ${BASH_VERSION}
4.0.33(1)-release
$ echo ${BASH_VERSION^^e}
4.0.33(1)-rElEasE
Readline
Interaktive Shells bearbeiten Eingaben mit der Readline-Library. Sie emuliert das Eingabeverhalten vom Emacs oder Vi und verarbeitet eine Reihe Eingabe auf spezielle Weise, in Teilen gesteuert durch Shell-Optionen oder spezielle Readline-Variablen. Diese stellen Sie in der Datei /etc/inputrc systemweit oder in ~/.inputrc (im Homeverzeichnis des Anwenders) lokal ein.
Die Syntax in dieser Datei weicht deutlich von der in anderen Konfigurationsdateien der Bash ab. Sie setzen die Readline-Variablen in diesen Dateien mit dem Set-Kommando (set show-all-if-ambiguous on) oder interaktiv mit dem Bind-Befehl (bind "set history-size 9999").
Letzteres erhöht beispielsweise die Readline-Variable history-size von den voreinstellten 500 auf 9999 Zeilen. Diese neue Readline-Variable definiert, wie viele Einträge die Bash in der History-Liste (die momentan bekannten History-Zeilen) speichert. Der Wert 0 sichert beliebig viele Einträge.
Mit Version 4 stehen einige neue Readline-Funktionen bereit. Emacs-Anwender kennen vermutlich die Funktion dabbrev-expand, die das aktuelle Wort (vor dem Cursor) aus den momentan vorhandenen Puffern heraussucht und komplettiert. Diese Funktion gibt es nun auch für die Bash, die Komplettierungen erfolgen aus der History.
Die neuen Funktionen shell-forward-word und shell-backward-word ermöglichen wortweises Springen, wobei sie die Shell-Metazeichen richtig berücksichtigen. Entsprechendes gilt für shell-backward-kill-word und shell-kill-word, die wortweise löschen und den Text dabei in den sogenannten Kill-Ring kopieren. Von dort holen Sie ihn bei Bedarf mit [Strg]+[Y] zurück. [Alt links]+[Y] rotiert den Kill-Ring um eine Position. Den Kill-Ring veranschaulicht diese Grafik:
Voreingestellt suchen Sie über [Strg]+[R] inkrementell rückwärts in der History. Das bedeutet, dass die Suche bei der Eingabe eines jeden Buchstabens genau das nächste zurückliegende Ergebnis zeigt, das mit allen eingegeben Zeichen übereinstimmt. Diese Methode erweist sich in der Praxis als sehr effektiv.
Die Shell ermöglicht aber auch eine nicht inkrementelle Suche in der History. Bei Bedarf binden Sie die entsprechenden Funktion an eine Tastenkombination Ihrer Wahl. Voreingestellt sind history-search-backward und history-search-forward ungebunden. Das zeigt der Bind-Befehl mit der Option -p an (Listing 11).
Listing 11
$ <$$b>bind -p<$$b> "\C-g": abort "\C-x\C-g": abort "\e\C-g": abort […] # history-search-backward (not bound) # history-search-forward (not bound) […] "\en": non-incremental-forward-search-history "\ep": non-incremental-reverse-search-history … "\e_": yank-last-arg
Auch hier gilt eine etwas ungewöhnliche Syntax: \C symbolisiert [Strg], \e_ steht für die Metataste, also entweder [Alt links] beziehungsweise ein vorab gedrücktes [Esc]. Die Sequenz \C\e_ entstünde also durch die Kombination [Strg]+[Alt links]+[Umschalt]+[-].
Die Readline-Funktion unix-filename-rubout ist neu. Sie wirkt ähnlich unix-word-rubout, wirkt aber nur bis zu einem Slash und den üblichen anderen Begrenzungszeichen, wie Leerzeichen. Die Readline-Variable mark-directories steuert, ob Readline bei der Anzeige von Verzeichnisnamen (etwa beim Komplettieren mittels Tabulator) einen Slash zum Unterscheiden des Dateinamens anfügt. Dieses Verhalten ist jetzt voreingestellt.
Die bereits bekannte Readline-Variable show-all-if-ambiguous hat eine Ergänzung bekommen: show-all-if-unmodified. Gesetzt zeigt Readline alle Möglichkeiten beim Komplettieren durch [Tab] sofort an, statt wie voreingestellt sich zunächst einmal mit der Bell (Displaybeep) zu beschweren. Das funktioniert aber nur, sofern nicht mehr als die voreingestellten 100 Möglichkeiten existieren.
Fazit
Seit mehr als 20 Jahren leistet die Bash als nützliches Programm auf vielen Systemen gute Dienste. Im Laufe der Jahre kamen viele, teils komfortable, teils nur noch für spezielle Anwendungen sinnvolle Erweiterungen hinzu. Daher fällt es zunehmend schwerer, die Übersicht über die vielen Features zu behalten. Es gilt also: Übung macht den Meister.






