Druckversion

Die Bourne Again Shell

Übersicht Weiter

Was ist die Bourne Again Shell?

Die Bash ist die mit Abstand beliebteste Shell unter Linux. Zum Teil mag das an ihrer frühen Verfügbarkeit liegen, während freie Implementierungen der Tcsh und Ksh für Linux erst nach zogen. Entscheidend scheint doch eher der enthaltene Funktionsumfang, den die anderen Shells nicht annähernd mit sich bringen und die Konformität mit dem IEEE POSIX P1003.2/ISO 9945.2 Shell und Tools Standard. Hierin ist auch der Grund für die Unterschiede zur Bourne Shell (sh bzw. bsh) zu sehen.

Auch wenn der Name Bash die enge Verwandtschaft zur Bsh nahe legt, so hat die GNU Bash gleich drei Vorfahren. Die eben genannte Bsh, die Csh und die Ksh. Von jedem erbte die Bash die Konstrukte, die sich aus der Erfahrung heraus als besonders nützlich erwiesen hatten.

Die Bash wird von der Free Software Foundation entwickelt und ist die Standardshell des Gnu-Betriebssystems HURD. Mit der Ende 1996 erschienenen Version 2.0 haben sich grundlegende Dinge gegenüber dem Vorgänger geändert. Wir werden uns ausschließlich auf die Bash >2.0 beziehen; einige Gaben sind gar dem neuesten Vertreter entliehen. Aktuelle Version ist 2.0.4.

Gliederung dieses Abschnitts

Vorab ein paar Worte zu den Überschriften und was Sie unter den einzelnen Punkten finden.

Die Fähigkeiten der Bash und Definitionen stellen einen Versuch der Erklärung dar, warum so zahlreiche Anwender die Bash als "die tolle Shell" an sich ansehen. Enthalten sind auch einige Begriffe, die wir nachfolgend verwenden. Der Erklärungsbedarf besteht, weil es uns nicht gelang, für manche Bezeichnung einen adäquaten deutschen Ausdruck zu finden.

Jedes nützliche Programm wird einmal gestartet werden. So beginnt die Exkursion mit dem Start der Bash. Hierbei geht es uns um die Optionen, über die die Bash beim Aufruf gesteuert werden kann und um interne Abläufe, die zumeist unbemerkt im Hintergrund geschehen. Mit dem Erscheinen der ersten Eingabeaufforderung hat die Startmaschinerie ihre Aufgabe erfüllt und hier endet auch der Einblick dieses Abschnitts.

Und beim Beenden der Bash geschehen auch noch Dinge, von denen der Anwender selten etwas bemerkt...

Syntax klingt stark nach trockener Theorie. Und mit jener beschäftigt sich auch dieser Abschnitt. Der Schwerpunkt liegt auf »korrekten Eingaben«. So wird bspw. das Erzeugen, Manipulieren und Löschen von Variablen behandelt; mit keinem Wort wird allerdings der Wirkungsbereich von Variablen erwähnt; da dies kein syntaktisches Problem darstellt.

Wesentlich ist das Verstehen der Betrachtung der Kommandozeile durch die Bash. Syntax zeigt auf, nach welchen Regeln die Shell die Eingabe bearbeitet, beschränkt sich jedoch auf die Benennung der Mechanismen. Expansionen schließlich durchleuchtet Regel für Regel und erschließt dem ambitionierten Shellprogrammierer sein künftiges Betätigungsfeld.

Initialisierungen behandelt alle Methoden, die zur Konfiguration der Laufzeitumgebung der Bash beitragen. Hierunter fallen die Konfigurationsdateien, aber auch die Variablen - diesmal im Kontext ihrer Wirkungsweise. Bash-interne Variablen aktivieren bestimmte Verhaltensweisen oder schalten sie ab (sollte Ihre Bash abweichende Reaktionen zeigen, könnte eine Shelloption dafür verantwortlich zeichnen). Des Weiteren gliedern sich Aliasse, Funktionen und eingebaute Kommandos in diesen Abschnitt ein.

Mit der Interaktiven Bash bewerten wir Methoden zur Navigation und Manipulation der Kommandozeile. Die Eingabehilfen passen ebenso in diese Thematik wie die Handhabung von Prozessen.

History wäre auch bei der Interaktiven Bash bestens aufgehoben. Jedoch sind die Möglichkeiten so weit reichend, dass wir dem Kommandozeilenspeicher einen eigenen Abschnitt widmen.

Fähigkeiten der Bash und Definitionen Zurück Anfang Weiter

Wichtige Fähigkeiten

Der Funktionsumfang der Bash ermöglicht sowohl ein komfortables Arbeiten als auch die Verrichtung komplexer Aufgaben. Hierzu zählen:

  • Das Editieren der Kommandozeile kann nach beliebigen Schemen erfolgen. Sowohl vi-, emacs, als auch nutzerdefinierte Modi sind möglich.
  • Die Bash besitzt einen Kommandozeilenspeicher (»History«). Alte Eingaben können gesucht, bearbeitet und erneut ausgeführt werden. Das konkrete Verhalten kann wiederum konfiguriert werden.
  • Prozesse können vom Benutzer gestartet, gestoppt und ihre Eigenschaften verändert werden.
  • Unvollständige Eingaben von Kommando- und Dateinamen u.a.m. können automatisch expandiert werden.
  • Geringe Fehler in der Eingabe können automatisch korrigiert werden
  • Das Eingabeprompt ist konfigurierbar.
  • Berechnungen analog zu C werden unterstützt.
  • Die Bash kennt Aliasse, Funktionen und Felder...

Einige Definitionen

Was es mit den knappen Aussagen konkret auf sich hat, soll Gegenstand der folgenden Abschnitte sein. Zuvor sollen benötigte Begriffe kurz erläutert werden.

Metazeichen

Ein Zeichen, das einzelne Worte trennt: |  &  ;  (  )  <  >  Leerzeichen  Tabulator

Name

Zeichenkette, bestehend aus alphanumerischen Zeichen und dem Unterstrich, wobei das erste Zeichen keine Ziffer ist.

Kontrolloperator

Ein Token, das eine spezielle Kontrollfunktion auslöst, die Symbole sind: |  &  &&  ;  ;;  (  )  Zeilenumbruch

Token

Eine Zeichenfolge, die von der Shell als eine Einheit betrachtet wird.

Whitespace

Steht für Leerzeichen und Tabulatoren und ggf. dem Zeilenumbruch.

Start der Bash Zurück Anfang Weiter

Unterteilung der Bash

Abbildung 1: Unterteilung der Bash

Interaktive und Nicht-Interaktive Bash

Interaktiv vs. Nicht-Interaktiv

Ist eine Shell mit der Standardein- und Standardausgabe (sprich »mit einer (virtuellen) Konsole«) verbunden, so bezeichnet man sie als interaktive Shell. Die interaktive Bash teilt sich wiederum ein in die Login Bash, die Nicht-Login Bash und die Restricted Bash. Alle drei unterscheiden sich im Einlesen von Initialisierungsdateien; letztere Bash schränkt die Befugnisse des Anwenders ein.

Wenn die Bash startet, entscheidet sie anhand des Ergebnisses von »tty -s«, ob es sich um eine interaktive Shell handelt (dann ist der Rückgabewert des Kommandos "0"). Eine Login-Shell führt die Kommandos aus der Datei /etc/profile und aus der ersten gefundenen Datei ~/.bash_profile, ~/.bash_login oder ~/.profile aus, sofern die Dateien existieren, lesbar sind und die Bash nicht mit der Option »--noprofile« gestartet wurde.

Eine Nicht-Login Bash liest die Datei ~/.bashrc. Mit der Option »--rcfile Diese_Datei« kann eine alternative Ressourcen-Datei benannt und mit »--norc« das Einlesen unterdrückt werden.

Eine Nicht-Interaktive Shell (typisch sind Shellskripte) wertet einzig die Umgebungsvariable BASH_ENV aus. Enthält sie den vollständigen Pfad zu einer Datei, so wird deren Inhalt gelesen und ausgeführt.

Optionen beim Start

Startoptionen

Die Bash ist ein Programm und nahezu jedes Programm unter Unix kann durch Optionen auf der Kommandozeile gesteuert werden. So kennt auch die Bash eine Reihe von Optionen, deren wichtigste vorgestellt sein sollen:


-c Kommandofolge

Die Bash liest und startet die Kommandos aus "Kommandofolge", welche als eine einzelne Zeichenkette anzugeben ist. Alles, was der Zeichenkette folgt, wird als Argument dem letzten Kommando der Kommandofolge übergeben:

user@sonne> bash date -u
date: /bin/date: cannot execute binary file
user@sonne> bash -c date -u
Sam Jul 8 10:16:17 MEST 2000

(Wird der Bash ein Argument übergeben, das keine Option ist, so interpretiert sie das Argument als Datei, die die Kommandos enthält. »date« ist natürlich keine Datei, die Kommandos beinhaltet, also beschwert sich die Bash...). Die Bash in Verbindung mit »-c« arbeitet als nicht-interaktive Shell.

-r bzw. --restricted

Die Bash arbeitet als »Restricted« Shell (siehe weiter unten).

-i

Die Bash arbeitet als interaktive Shell, d.h. die Standardein- und -ausgabe sind mit einem Terminal verbunden.

--login

Die Bash arbeitet wie eine Login-Shell, d.h. alle Konfigurationsdateien werden eingelesen (Eine Login-Shell hat nichts mit der Anzeige einer Login-Aufforderung zu tun, sie nennt sich nur so, weil die einzulesenden Dateien zumeist einmalig beim Start einer Sitzung geladen werden.).

--noprofile

Unterdrückt das Einlesen der profile-Dateien.

--norc

Unterdrückt das Einlesen der »~/.bashrc«.

--posix

Die Bash verhält sich wie eine POSIX-Shell. Sie liest hierzu einzig die in der Umgebungsvariablen ENV angegebene Datei ein. Wird die Bash über den Link »sh« gerufen, startet sie ebenso im POSIX-Modus.

--rcfile Datei

Eine nicht-interaktive Bash liest die angegebene Datei ein (anstatt ~/.bashrc)

-v bzw. --verbose

Schaltet die erweiterte Ausgabe ein. Sinnvoll ist diese Option in der Testphase von Shellskripten, um jede Zeile, die ausgeführt wird, angezeigt zu bekommen.

Als weitere Optionen können zahlreiche Flags bereits beim Start der Bash aktiviert werden. Da dies auch bei laufender Shell geschehen kann - und dies der gebräuchliche Weg ist - behandeln wir die Flags erst im Zusammenhang mit dem builtin-Kommando set.

Bash als Login-Shell

Login-Bash

In den meisten Konfigurationen, die die Distributoren ausliefern, dürfte die Bash als Login-Shell voreingestellt sein. Ist dem nicht so, kann der Benutzer mit Hilfe des Kommandos chsh die Bash in die Passwortdatei eintragen.

Dies funktioniert jedoch nur, wenn die Bash in der Datei /etc/shells eingetragen ist. Ist dem nicht so, könnte einem entweder der Administrator aus der Patsche helfen, indem er den Eintrag in obiger Datei ergänzt oder man baut in einem der Startup-Skripte seiner bisherigen Login-Shell den Aufruf »exec /usr/bin/bash --login« ein.

Bei dem zuletzt beschriebenem Vorgehen sollten Sie sicher stellen, dass das Skript mit dem Aufruf nicht auch von der Login-Bash ausgeführt wird, sonst hängt die Shell in einer Schleife mit Reinitialisierungen fest. Am besten fragen Sie im Skript ab, ob der Name des aktuell ausgeführten Programms die Bash ist:

...
case "$0" in
   *bash )
      ;;
   * )
      test -x /usr/bin/bash && exec /usr/bin/bash --login
      ;;
esac
...

Restricted Bash

Restricted Bash

Mit dem Argument -r gestartet, arbeitet die Bash als restricted Shell. Dasselbe Verhalten kann erreicht werden, wenn ein Link rsh bzw. rbash auf die Bash verweist und diese über den Link gerufen wird.

Das Anliegen einer »eingeschränkten Shell« ist, einen Anwender in seinen Befugnissen weiter einzuschränken. Unter einer restricted Bash ist es nicht möglich:

  • Das Arbeitsverzeichnis zu wechseln:

    user@sonne> cd ..
    bash: cd: restricted


  • Die Variablen SHELL, PATH, ENV und BASH_ENV zu verändern:

    user@sonne> export PATH=./:$PATH
    bash: PATH: readonly variable
    user@sonne> unset SHELL
    bash: unset: SHELL: cannot unset: readonly variable


  • Einen Kommandoaufruf mit einem enthaltenen Schrägstrich (Slash) abzusetzen (es können also nur Kommandos ausgeführt werden, die in den Pfaden der Variable »PATH« enthalten sind):

    user@sonne> /bin/ls
    bash: /bin/ls: restricted: cannot specify `/' in command names


  • Mit dem Punkt-Kommando eine Datei auszuführen, deren Angabe einen Slash enthält:

    user@sonne> . /etc/profile
    bash: .: /etc/profile: restricted


  • Die Umleitung der Ein- und Ausgaben vorzunehmen:

    user@sonne> ls > bla
    bash: bla: restricted: cannot redirect output


  • Während des Starts der restricted Bash Funktionen zu importieren und Shelloptionen zu setzen
  • Mit dem exec-builtin-Kommando die Bash durch ein anderes Programm zu ersetzen
  • builtin-Kommandos zu aktivieren bzw. zu deaktivieren (also ihren momentanen Status zu ändern)
  • Den restricted-Modus abzuschalten
  • Das Bash-builtin-Kommando command -p anzuwenden

Bei den Einschränkungen scheint eine Erlaubnis zum Zugang einer »nicht-vertrauenswürdigen« Person unkritisch zu sein. Doch Vorsicht! Stellen Sie sicher, dass die exportierte PATH-Variable keine Verzeichnisse enthält, die eine Shell enthalten. Es ist durchaus sinnvoll, zu diesem Zweck ein eigenes Binaryverzeichnis mit »unkritischen« Kommandos (analog zu FTP) anzulegen und einzig dieses in PATH aufzunehmen. Allerdings gibt es eine Reihe Kommandos (bspw. Editoren), die intern das Eröffnen einer Shell unterstützen und somit das Umgehen der Restriktionen ermöglichen. So ist es erlaubt, im vi eine »normale« Bash zu starten, selbst wenn der Editor innerhalb einer restricted Shell gestartet wurde (neuere Versionen starten wiederum nur eine restricted Bash)!

Beenden der Bash Zurück Anfang Weiter

Handelt es sich um eine Login-Shell, so lässt sich die Bash sogar vor ihrem Ende zu einem letzten Groß-Reine-Machen motivieren. Alle Aktionen, die die Shell vor ihrem Ableben noch erledigen soll, sind hierzu in eine Datei .bash_logout zu schreiben.

Ob die Bash nun mittels exit oder logout verlassen oder sie mit einem Signal (Ausnahme: KILL) zum Ende gebracht wird, so wird sie abschließend noch das ihr auferlegte Pensum erledigen.

Die Bash muss nicht unbedingt verlassen werden. Ein Ersetzen des Programms durch ein anderes mittels exec kommt einer Beendigung gleich. exec wird im Zusammenhang mit eingebauten Kommandos behandelt.

Syntax der Bash Zurück Anfang Weiter

Es ist schier unmöglich, ein komplexes Thema wie die Bash »in einem Fluss« darzulegen. Nahezu jedes Konzept steht in Wechselwirkung mit einem anderen, nicht unwesentlicheren Aspekt. Mit welchem sollte man beginnen?

Bevor uns die praktischen Dinge der Bash den Kopf zerbrechen, steht deshalb die (trockene) Theorie der allgemeinen Syntax voran, natürlich garniert mit (hoffentlich) sinnvollen Beispielen. Und hier erwächst ein weiteres Problem. Wie soll ein Beispiel praktisch sein, wenn wir uns auf das bisherige Wissen beschränken?

Indem wir die Schranken missachten und den Leser auf den weiteren Text verweisen, der die fehlenden Wissenslücken schließen wird.

Der weitere Text erklärt:

Kommentare

Normalerweise leitet das Doppelkreuz # in der Bash einen Kommentar ein. Alles, was diesem Zeichen folgt, wird von der Bash ignoriert bis zum nächsten Zeilenumbruch. Dies gilt sowohl für die interaktive als auch für die nicht-interaktive Bash.

Eine Ausnahme sind Shellskripte, in denen die erste Zeile mit #!... beginnt. Für die Bash ist es die Anweisung, die nachfolgenden Zeilen mit dem hinter »#!« angegebenen Interpreter (eine Shell, Perl, awk, ...) auszuführen.

Die Bedeutung von # kann entweder durch Quoten (siehe später) oder durch die Shelloption "interactive_comments" aufgehoben werden.

user@sonne> cat \#script
#!bin/sh
echo "sinnlos"
user@sonne> #script
user@sonne>
# Wirkung des Doppelkreuzes aufheben:
user@sonne> shopt -u interactive_comments
user@sonne> #script
sinnlos
# Wirkung des Doppelkreuzes aktivieren:
user@sonne> shopt -s interactive_comments

Variablen

Der Bezeichner einer Variable ist ein Name (vergleiche Definitionen). Die Anzahl Zeichen, die für einen Bezeichner signifikant sind, ist von der konkreten Bash-Version abhängig. Sie können aber davon ausgehen, dass Sie beim Eingeben eines langen Bezeichners eher ermüden, als dass die Bash kapituliert.

Im Sinne der Syntax werden skalare und Feldvariablen unterschieden. Zum Erzeugen von Variablen existieren mehrere Möglichkeiten. Die einfachste zum Anlegen einer skalaren Variable ist:

user@sonne> variablen_name=wert

Eine Feldvariable (Array) erzeugen Sie mittels

user@sonne> feld_var_name[index]=wert
# oder
user@sonne> feld_var_name=(wert1 wert2 wert3)

Den Inhalt einer Variable betrachten Sie mit Hilfe des Kommandos echo:

user@sonne> echo $variablen_name
wert
user@sonne> echo $feld_var_name
wert1
user@sonne> echo $feld_var_name[2]
wert1[2]
user@sonne> echo ${feld_var_name[2]}
wert3

Bemerkung: Wenn Sie auf den Inhalt einer Feldvariable zugreifen, müssen Sie durch Klammerung klar stellen, dass Sie den Inhalt eines konkreten Elementes meinen. Ohne explizites Setzen der Klammern wird als Index implizit »0« angenommen; daraus resultiert die Ausgabe von »echo $feld_var_name[2]«.

Eine Variable können Sie mit einem Attribut versehen. Hierzu verwenden Sie declare oder typeset. Beide Kommandos besitzen dieselben Optionen und Wirkungen - wir beschränken uns daher auf ersteres. Vorsicht: Entgegen aller Logik setzt »-« ein Attribut und »+« löscht es!

-a

Setzt das Feldattribut einer Variable (wird einer Variable ein Feld zugewiesen, wird das Attribut automatisch gesetzt)

[-/+] f

Zeigt eine Funktionsdefinition an/zeigt sie nicht an:

user@sonne> first_func() {echo "erste Funktion";}
user@sonne> first_func
erste Funktion
user@sonne> declare -f first_func
first_func()
{
echo "erste Funktion"
}

[-/+] i

Setzt/Löscht das Integer-Attribut. Für so deklarierte Variablen ist eine einfachere Syntax für Arithmetik erlaubt.

user@sonne> declare -i a=3
user@sonne> b=3
user@sonne> a=a+b
user@sonne> b=a+b
user@sonne> echo $a "   " $b
6    a+b

-p

Zeigt die Attribute und den Inhalt einer Variablen an.

user@sonne> declare -p a
declare -i a="6"
user@sonne> declare -p SHELL
declare -x SHELL="/bin/bash"

[-/+] r

Setzt das Leseattribut/verwirrt den Leser. Ist es aktiv, kann der Wert einer Variablen nicht verändert werden. Die Variable kann weder gelöscht, noch kann ein Lese-Attribut entfernt werden:

user@sonne> declare -r a
user@sonne> a=nix
bash: a: readonly variable
user@sonne> declare +r a
bash: declare: a: readonly variable

[-/+] x

Setzt/Löscht das export-Attribut. Eine exportierte Variable ist auch in den Nachfahren des die Shell ausführenden Prozesses sichtbar (Siehe unter Shellvariablen).

Eine Variable kann auch gelöscht werden. Hierzu dient das builtin-Kommando unset:

user@sonne> unset a
bash: unset: a: cannot unset: readonly variable
user@sonne> unset b
user@sonne> declare -p b
bash: declare: b: not found

Expansion vor der Wertzuweisung an eine Variable

Bevor ein Wert einer Variablen zugewiesen wird, versucht die Bash diesen Wert nach bestimmten Regeln zu substituieren. Dabei durchläuft die Bash folgende Schritte in beschriebener Reihenfolge:

  1. Tildeexpansion
  2. Parameter- und Variablenexpansion
  3. Kommandosubstitution
  4. Arithmetische Substitution
  5. Entfernen der »Quoting«-Zeichen

Erst jetzt erfolgt die tatsächliche Zuweisung an die Variable.

Was sich hinter den einzelnen Expansionen verbirgt, soll im Anschluss an diesen Abschnitt betrachtet werden.

Einfache Kommandos

Der Begriff des »einfachen Kommandos« definiert einen wichtigen Aspekt der Semantik der Bash. Ein »einfaches Kommando« ist ein Ausschnitt der Kommandozeile, den die Bash in einem Zusammenhang betrachtet. Dazu parst die Shell zunächst die Eingabe und extrahiert alle enthaltenen »einfachen Kommandos«, für die sie nachfolgend definierte Substitutionen vollzieht.

Ein einfaches Kommando wird stets durch einen Kontrolloperator abgeschlossen (Definitionen). Es besteht aus einer Sequenz optionaler Variablenzuweisungen, von durch Whitespaces getrennten Worten und Ein-/Ausgabe-Umleitungen. Das erste Wort, das keine Variablenzuweisung ist, wertet die Bash als Kommandoname, alle weiteren Worte sind die Argumente des Kommandos.

Anmerkung: Man kann sich als einfaches Kommando den Abschnitt der Kommandozeile vorstellen, der innerhalb ein und desselben Prozesses ausgeführt wird.

Die einzelnen Substitutionsschritte, die die Bash für jedes einfache Kommando vornimmt, sind:

  1. Variablenzuweisungen und Ein-/Ausgabe-Umleitungen werden markiert (damit sie nicht im folgenden Schritt betrachtet werden)
  2. Alle nicht-markierten Worte werden expandiert (siehe nachfolgend)
  3. Ein- und Ausgabe-Umleitungen werden vorbereitet (indem die entsprechenden Dateideskriptoren geschlossen/geöffnet werden)
  4. Die Variablenzuweisungen werden vorgenommen (inklusive vorheriger Expansionen)

Expansion von Worten

Jede durch Metazeichen getrennte Zeichenkette bildet ein einzelnes Wort in der Eingabe und wird von der Bash nach bestimmten Regeln expandiert. Die Reihenfolge der Auswertung ist:

  1. Klammernexpansion
  2. Tildeexpansion
  3. Parameter- und Variablenexpansion
  4. Kommandosubstitution
  5. Arithmetische Substitution
  6. Wortzerlegung
  7. Pfadnamensexpansion

Was sich hinter den einzelnen Expansionen verbirgt, soll im Anschluss an diesen Abschnitt betrachtet werden.

Rückgabestatus eines Kommandos

Mit Beendigung eines Kommandos liefert dieses einen Statuswert an seinen Elternprozess. Dieser Wert kann eine Zahl zwischen 0 und 255 beinhalten und gibt Auskunft über den Erfolg (Rückgabewert »0«) und Misserfolg (Rückgabewert »> 0«) der Ausführung des Kommandos. Viele Kommandos kodieren im Wert die vermutliche Ursache des Scheiterns, wobei Werte zwischen 0 und 127 empfohlen sind. Wird ein Kommando »von außen« beendet (kill), so ist der Rückgabewert die Signalnummer + 128. Die Shell speichert den Rückgabestatus des letzten Kommandos in der speziellen Variable $?.

user@sonne> grep root /etc/passwd
root:x:0:0:root:/root:/bin/bash
user@sonne> echo $?
0
user@sonne> grep bla /etc/passwd
user@sonne> echo $?
1
user@sonne> grep root /ETC/passwd
grep: /ETC/passwd: Datei oder Verzeichnis nicht gefunden
user@sonne> echo $?
2

Pipelining

In einer Pipeline werden mehrere Kommandos mittels des Pipesymbols | miteinander verknüpft. Jedes Kommando wird hierbei in einer eigenen Prozessumgebung ausgeführt. Das Kommando links eines Pipesymbols schreibt seine Ergebnisse nachfolgend in die Pipe (»Röhre«) anstatt auf die Standardausgabe. Ein rechts des Symbols stehendes Kommando bezieht seine Eingabedaten aus der Pipe.

Die Pipeline ist somit eine wichtige Unterstützung des Unix-Werkzeugkasten-Prinzips durch die Bash, womit durch geschickte Kombination der scheinbar simplen Kommandos komplexe Aufgaben lösbar werden.

Die vermutlich verbreitetste Anwendung von Pipelines ist die Umleitung von Ausgaben eines Kommandos in einen Pager, um diese bequem seitenweise betrachten zu können:

user@sonne> ls -l /dev | less

Ohne den Pager hätte man in obigem Beispiel keine Möglichkeit, die ca. 1500 Ausgabezeilen von ls am Bildschirm zu verfolgen.

Eine komplexere Anwendung ist die nächste Verknüpfung zweier tar-Befehle, womit ein ganzes Verzeichnis bei Beibehaltung der Eigentümer kopiert wird:

user@sonne> tar cf - ./bla | (cd /zielpfad; tar xf -)

Listen

In Erweiterung des Pipeline-Konzepts lassen sich mehrere Kommandos auf weitere Arten in einer Kommandozeileneingabe angeben. Im Sprachgebrauch der Bash werden diese Varianten als Listen zusammengefasst:

Kommando_1 ; Kommando_2

Führt die Kommandos nacheinander aus. Kommandos, die unabhängig voneinander arbeiten, lassen sich somit bequem durch Semikola getrennt angeben. Das jeweils nächste Kommando wird unverzüglich gestartet, sobald das vorherige terminierte.

Kommando_1 && Kommando_2
Kommando_1 || Kommando_2

Führt Kommando_2 nur aus, wenn Kommando_1 erfolgreich (&&) bzw. nicht erfolgreich (||) war.

Insbesondere in Shellskripten wünscht man oft die bedingte Ausführung, d.h. ein Kommando soll in Abhängigkeit vom (Miss)Erfolg eines anderen Kommandos gestartet werden. Als Beispiel soll eine Datei mit dem Kommando rm gelöscht werden. Damit rm allerdings keine Fehlermeldung erzeugt, soll es nur ausgeführt werden, wenn wir die Berechtigung dafür besitzen und die Datei auch existiert. Eine Erfolgsmeldung kann auch nicht schaden... Der Aufruf könnte wie folgt formuliert werden:

user@sonne> test -w bla && rm bla && echo "Datei entfernt"

Vielleicht sollte die Datei »bla« zur Demonstration des Beispiels angelegt werden? Allerdings nur, wenn sie noch nicht existiert:

user@sonne> test -e bla || touch bla
user@sonne> test -w bla && rm bla && echo "Datei entfernt"
Datei entfernt

Kommando &

Führt das Kommando als Hintergrundprozess aus.

Schickt man ein Kommando in den Hintergrund (indem ihm ein & nachgestellt wird), so wartet die Shell nicht mehr aktiv auf dessen Terminierung, sondern schreibt sofort wieder das Prompt aus und nimmt neue Eingaben entgegen. Das im Hintergrund tätige Kommando ist von der Standardeingabe abgeschnitten. Einzig die Ausgaben landen weiterhin auf dem Bildschirm.

user@sonne> sleep 100 &
[1] 956
user@sonne>

Die Ausgabe betrifft die Job- und die Prozessnummer des Hintergrundprozesses (1 bzw. 956). Irgendwann wird der Prozess seine Arbeit erledigt haben, dann erscheint mit der nächsten Eingabe folgende Ausschrift:

user@sonne> "beliebige Eingabe" [Enter]
[1]+  Done             sleep 100

Folgt dem & ein weiteres Kommando, wird dieses sofort gestartet. Somit lassen sich Kommandos quasi parallel ausführen (zur Demonstration greifen wir auf die Verwendung von Subshells zurück):

user@sonne> (sleep 6; echo -n "[1] "; date) & (echo -n "[2] "; date)
[1] 14914
[2] Die Okt 17 13:58:45 MEST 2000
[1] Die Okt 17 13:58:51 MEST 2000

{ Kommando;}

Startet das Kommando innerhalb der aktuellen Shell. Sinn dieses Konstrukts ist die Gruppierung mehrerer Kommandos, sodass sie eine gemeinsame Ausgabe erzeugen:

user@sonne> ls -l | head -1; date > bla
insgesamt 763
user@sonne> cat bla
Die Okt 17 18:40:40 CEST 2000
user@sonne> { ls -l | head -1; date;} > bla
user@sonne> cat bla
insgesamt 763
Die Okt 17 18:40:40 CEST 2000

Erläuterung: Die erste Kommandozeile leitet nur die Ausgabe von date in die Datei »bla« um, deshalb landet die Ausgabe von »ls -l | head -1« auf der Standardausgabe. Die zweite Zeile gruppiert beide Kommandos, sodass die Ausgabe komplett in die Datei umgeleitet wird.

(Kommando)

Startet das Kommandos innerhalb einer neuen Shell.

Die letzte Variante zur Eingabe einer Kommandosequenz betrifft deren Ausführung in einer Subshell. D.h. die Bash startet als Kindprozess eine weitere Shell und übergibt dieser die auszuführenden Kommandos. Das Verhalten ähnelt stark der Gruppierung mittels geschweifter Klammern und tatsächlich lassen sich dieselben Wirkungen erzielen:

user@sonne> pwd; date > output
/home/user
user@sonne> cat output
Don Jun  8 09:38:23 MEST 2000

user@sonne> (pwd; date) > output
user@sonne> cat output /home/user
Don Jun  8 09:38:27 MEST 2000

Aus Effizienzgründen sollte, wann immer es möglich ist, auf die Subshell verzichtet werden (Erzeugen eines Prozesses kostet immer Zeit). Notwendig kann sie allerdings werden, wenn verschiedenen Kommandos auf dieselben lokalen Variablen zugreifen. Durch Start in einer eigenen Shell sind Wechselwirkungen ausgeschlossen.

Der Rückgabewert einer Subshell ist immer »0« und die in ihr bearbeiteten Kommandos haben keinen Einfluss auf den Elternprozess. So kann eine Subshell verwendet werden, um ein Kommando in einem anderen Verzeichnis auszuführen, ohne dass das Verzeichnis in der aktuellen Shell gewechselt wird:

user@sonne> pwd
/home/user
user@sonne> (cd /usr/X11R6/bin; pwd)
/usr/X11R6/bin
user@sonne> pwd
/home/user

Ein/Ausgabeumleitung

Mit dem Start der Bash öffnet diese die drei Standarddateien mit den Dateideskriptoren 0 (stdin), 1 (stdout) und 2 (stderr). Die Standardeingabe ist dabei zunächst mit der Tastatur verbunden, Standard- und Standardfehler-Ausgabe schreiben initial auf den Bildschirm.

Mit den Mechanismen der E/A-Umleitung lassen sich alle drei Kanäle (genau genommen jeder beliebige Dateideskriptor) in Dateien umlenken, so dass ein Kommando seine Eingaben aus einer Datei bezieht oder die Ausgaben bzw. Fehler in eine solche gelenkt werden. Die Sonderzeichen zur E/A-Umleitung sind:

[n]< Datei

Umleitung der Standardeingabe. Rein interaktiv arbeitende Kommandos erwarten ihre Eingaben von der Standardeingabe. Bestes Beispiel ist das Kommando mail, das den Text einer Nachricht prinzipiell von der Standardeingabe liest. Mail möchte man allerdings häufig automatisch erzeugen und den Inhalt einer Datei versenden; hier kann die Eingabeumleitung die Lösung bedeuten:

user@sonne> mail tux@melmac.all -s Wichtig < Message.txt

Im Beispiel liest das Kommando mail tatsächlich von Dateideskriptor 0, wohinter sich normalerweise die Standardeingabe verbirgt. Möglich wird dies, indem die Bash vor der Kommandoausführung die Standardeingabe schließt, d.h. den Dateideskriptor 0 frei gibt, und der erste freie Deskriptor (also nun 0) beim anschließenden Öffnen der Datei "Message.txt" an diese vergeben wird.

Links des Umleitungssymbols kann sogar das Ziel der Umleitung fehlen, dann steht der Inhalt der umgeleiteten Datei direkt in der Standardeingabe. Eine »sinnvolle« Anwendung ist mir bislang nicht in die Quere gekommen, deswegen soll die obskure Syntax eines Kopiervorgangs die Idee verdeutlichen:

user@sonne> < foo cat > bla

[n]> Datei

Umleitung der Ausgabe in eine Datei. Existiert die Zieldatei nicht, wird sie automatisch erzeugt. Ist sie bereits vorhanden, wird sie geöffnet und ihr alter Inhalt komplett verworfen (über Shelloptionen kann das Überschreiben verhindert werden; allerdings nicht, wenn anstatt »>« »>|« verwendet wird).

Mit der optionalen Angabe von »n« kann der umzuleitende Deskriptor angegeben werden; ohne Angabe wird implizit »1« angenommen, was die Standardausgabe betrifft:

# Umleitung der Ausgabe von "ls -lt" in eine Datei
user@sonne> ls -lt > time_sorted

# Provozieren eines Fehlers
user@sonne> touch /etc/passwd
touch: /etc/passwd: Keine Berechtigung

# Umleitung des Fehlers
user@sonne> touch /etc/passwd 2> /dev/null

# Die Shelloption "noclobber" verhindert das Überschreiben existierender Dateien:
user@sonne> set -o noclobber
user@sonne> ls -lt > time_sorted
bash: time_sorted: cannot overwrite existing file
# "noclobber" zurüsetzen:
user@sonne> set +o noclobber

[n]>> Datei

Umleitung der Standardausgabe, wobei die Ausgaben an die Zieldatei angehangen werden. Die Arbeitsweise ist analog zur »normalen« Ausgabeumleitung, außer dass im Falle einer existierenden Datei die neuen Daten an deren Ende angehangen werden.

&> Datei bzw. >& Datei

Umleitung der Standard- und Standardfehlerausgabe. Beide Varianten der Angabe sind semantisch äquivalent; das Manual zur Bash empfiehlt die erstere Form. Eine nützliche Anwendung dieser Umleitung findet man im Zusammenhang mit Shellskripten, wo man oftmals einzig am Rückgabewert eines Kommandos, nicht aber an dessen Ausgabe interessiert ist. Als Beispiel testet die folgende Kommandofolge, ob der Benutzer "tux" Mitglied der Gruppe "fibel" ist:

user@sonne> { groups tux | grep fibel;} &> /dev/null && echo "Mitglied"

"Here-Document"

Diese Form der Umleitung ermöglicht die Zusammenfassung mehrerer Zeilen als Eingabe für ein Kommando. Normalerweise ist mit Eingabe des Zeilenumbruchs die Eingabe für die Bash beendet, beim so genannten »Here-Document« wird eine Eingabe-Ende-Kennung vorab vereinbart und alle nachfolgenden Zeilen bis zum Erscheinen der Kennung auf einer neuen Zeile werden einem Kommando als Eingabe zugeführt. Die allgemeine Syntax lautet:

Kommando << 'Das ist die Endekennung'
   Hier folgt das eigentliche "Here-Document",
   das sich über beliebig viele Zeilen
   erstrecken kann
Das ist die Endekennung

Als Endekennung können Sie jede Zeichenkette verwenden, die im folgenden Dokument nicht allein auf einer Zeile existiert. Die Endekennung wird dabei weder der Parameter-, der Kommando-, der Pfadnamen-, noch der arithmetischen Expansion unterzogen. Indem Sie die Kennung quoten, beugen Sie auch den anderen Expansionen vor.

Wurde die Kennung nicht gequotet, werden die Zeilen des »Here-Documents« der Parameter-, der Kommando- und arithmetischen Expansion unterzogen, sonst wird keinerlei Expansion vorgenommen:

user@sonne> cat << bla
>$(ls -C)
>bla
a.out   bla   foo   ...

user@sonne> cat << 'bla'
>$(ls -C)
>bla
$(ls -C)

Sie werden sich fragen, wo man eine solche Anwendung sinnvoll anwenden könnte? Beispiele gibt es genügend, bspw. um aus einem Shellskript heraus eine Datei anzulegen.

Vielleicht haben Sie bereits Software eigenständig kompiliert? Dann sind Ihnen sicher die »configure«-Aufrufe bekannt. Diese testen die Umgebung des Systems. U.a. stellen sie fest, welche Werkzeuge vorhanden sind und generieren daraus die späteren Makefiles. Das nachfolgende Skript testet, ob es sich bei dem installierten Kompiler »cc« um einen GNU-Compiler handelt. Es legt hierzu eine Datei mit einem »Define« an, das nur ein GNU-Compiler kennt. Anschließend wird der Präprozessor des Kompilers gestartet und in dessen Ausgabe gesucht, ob der durch das »Define« geschachtelte Ausdruck übernommen wurde:

#/bin/sh

cat > out.c << 'ENDE DER TESTDATEI'
#ifdef __GNUC__
   yes
#endif
ENDE DER TESTDATEI

{ cc -E out.c | grep yes; } &> /dev/null && echo "Gnu-Compiler"

rm out.c

Duplizieren von Dateideskriptoren

Mit den bisherigen Mitteln war es nur möglich, zu einem Zeitpunkt aus einer Quelle in eine geöffnete Datei zu schreiben bzw. aus dieser zu lesen. D.h. ein Dateideskriptor war exklusiv nutzbar. Indem ein solcher Deskriptor verdoppelt wird, lässt sich diese Beschränkung aufheben.

Die Eingabe lässt sich mit folgendem Konstrukt duplizieren:

[n]<&Deskriptor

Ohne Angabe von »n« ist die Standardeingabe gemeint, sonst kann »n« jeder zum Lesen geöffnete Deskriptor sein.

Analog dazu verdoppelt folgende Angabe einen Ausgabedeskriptor:

[n]>&Deskriptor

Mit dem Mechanismus der Ausgabeverdopplung ließe sich die gleichzeitige Umleitung von Standardausgabe und -fehler auch wie folgt formulieren:

user@sonne> grep root /etc/* > output 2>&1

Erläuterung: Der Ausdruck »2>&1« kann wie folgt interpretiert werden: »Sende die Standardfehlerausgabe (2) dorthin, wohin die Standardausgabe (1) geht.«. Implizit wird eine Kopie des Ausgabedeskriptors erzeugt. Beachten Sie, dass die Bash die Umleitungen in der Kommandozeile von links nach rechts bewertet. Vertauschen Sie in obigem Beispiel die beiden Umleitungen:

# FALSCH
user@sonne> grep root /etc/* 2>&1 > output

so landen die Fehlermeldungen auf dem Bildschirm und nur die normalen Ausgaben in »outfile«. Zum Zeitpunkt der Umleitung der Fehler auf die Standardausgabe zeigt diese noch auf die »normale« Ausgabedatei (Terminal-Device). Die Fehler werden demzufolge auf eine Kopie des Deskriptors gelenkt, der das Terminal geöffnet hat. Erst nachfolgend wird der Originaldeskriptor auf die Datei »umgebogen«.

Bleibt die Frage nach dem Nutzen derartiger Konstrukte... aber gerade das letzte Beispiel offenbart eine trickreiche und dennoch sinnvolle Anwendung. Die "normalen" Ausgaben laufen unter Unix i.d.R. gepuffert ab, einzig Fehler finden ohne Verzögerung ihren Weg auf die Konsole. Eine Variante, die Standardausgabe ungepuffert dem Bildschirm zukommen zu lassen, wäre, diese auf Standardfehler umzuleiten. Doch wäre es nun wünschenswert, die Fehler auf einen anderen Kanal zu dirigieren. Folgendes Beispiel realisiert die notwendigen Umleitungen:

# Standardausgaben auf Fehlerausgabe umleiten, Fehler selbst nach /dev/null
user@sonne> find / -name "*bla*" >&2 2> /dev/null

Und wie leitet man die normalen und die Fehlerausgaben eines Kommandos in die Eingabe eines anderen? Mit folgender Notation:

# Standardausgaben und Fehlerausgabe in eine Pipe
user@sonne> find / -name "*bla*" 2>&1 | less

Um einzig die Fehlerausgaben in eine Pipe zu speisen, hilft folgendes Vorgehen:

# Standardfehlerausgabe in eine Pipe
user@sonne> find / -name "*bla*" 3>&1 1>&2 2>&3 | less

Öffnen von Deskriptoren zum Schreiben und Lesen

[n]<>Deskriptor

Eine Beispiel zur Anwendung wird im Zusammenhang mit dem eingebauten Kommando exec gegeben.

Bedingte Ausführung

Die auch als Flusskontrolle bekannten Mechanismen ermöglichen eine kontrollierte Beeinflussung des Programmablaufs. Die Bash stellt die if...fi und case...esac-Konstrukte zur Verfügung.

Erstere Form wird meist zur Unterscheidung einiger weniger Fälle (meist 2) verwendet. Die Syntax lautet:

if Liste von Kommandos; then
   Liste von Kommandos
[elif Liste von Kommandos; then
   Liste von Kommandos]
[else
   Liste von Kommandos]
fi

Von den angegebenen Zweigen werden die Kommandos höchstens eines Zweiges ausgeführt. Entweder die des ersten Zweiges, dessen Bedingung erfüllt ist oder der optionale "else"-Zweig, falls keine Bedingung erfüllt wurde. Die Bedingung selbst ist der Rückgabewert der Liste der Kommandos (meist also der Rückgabewert des letzten Kommandos der Liste).

Das "case"-Konstrukt wird bei einer höheren Anzahl an Auswahlkriterien bevorzugt. Prinzipiell kann mittels "case" jede "if"-Bedingung abgebildet werden. Ein wesentlicher Unterschied ist die mögliche Abarbeitung mehrerer Fälle, da alle Anweisungen ab der ersten zutreffenden Bedingung bis zu einem expliziten Verlassen des Konstrukts ausgeführt werden (d.h. ist eine Bedingung erfüllt, werden die nachfolgenden ignoriert).

case Bedingung in
   Muster [ | Muster ] )
      Liste von Kommandos
      [;;]
  [Muster [ | Muster ] )
      Liste von Kommandos
      [;;]]
esac

Die Bedingung muss ein Token sein. Die Muster unterliegen denselben Expansionen wie Pfadnamen und dürfen somit Metazeichen enthalten. Stimmt ein Muster mit der Bedingung überein, werden alle nachfolgenden Kommandos bis zum Verlassen des Konstrukts mittels ";;" oder bis zum abschließenden "esac" ausgeführt.

Der typische Anwendungsbereich für "if"- und "case"-Konstrukte ist die Shellprogrammierung und in diesem Zusammenhang werden Ihnen noch genügend Beispiele zur Benutzung begegnen.

user@sonne> if test $( id | awk -F'[=(]' '{print $2}'; ) -eq "0"; then echo Superuser; else echo Normaler User; fi
Normaler User
user@sonne> su -
Password:
root@sonne> if test $( id | awk -F'[=(]' '{print $2}'; ) -eq "0"; then echo Superuser; else echo Normaler User; fi
Superuser

Das (zugegeben... etwas konstruierte) Beispiel entscheidet, ob der aufrufende Benutzer als Root oder als "normaler" Nutzer arbeitet. Die Verwendung des builtin-Kommandos test ist typisch für Bedingungen, wir werden es später im Kontext der eingebauten Kommandos ausführlich kennen lernen. Details zu awk finden Sie im gleichnamigen Abschnitt.

Schleifen

Die Bash bietet vier Typen der wiederholten Kommandoausführung. Die "for"-Schleife wird verwendet, um eine Kommandosequenz n-mal auszuführen, wobei die Anzahl vorab fest steht. Im Unterschied dazu wiederholt die "while"-Schleife die Liste der Kommandos nur so oft, solange die angegebene Bedingung erfüllt ist. "until"-Schleifen sind genau genommen nichts anderes als "while"-Schleifen, wobei die Bedingung negiert wurde, d.h. sie wird solange durchlaufen, bis die Bedingung wahr wird. Schließlich helfen die "select"-Schleifen beim Erzeugen von Auswahlmenüs für Benutzereingaben.

Innerhalb jeder Schleife kann diese durch den Aufruf von break verlassen werden. Ein enthaltenes continue veranlasst das Überspringen nachfolgender Befehle und Fortfahren mit dem nächsten Schleifendurchlauf.

for Variable [ in Wortliste ]; do
    Liste von Kommandos
done

Die for-Schleife wird genau so oft durchlaufen, wie Einträge in der Wortliste stehen. Im ersten Durchlauf wird der erste Eintrag der Wortliste an Variable zugewiesen, im zweiten der zweite Eintrag usw.:

user@sonne> for i in a b c; do echo $i; done
a
b
c

Die Wortliste wird zunächst expandiert (dieselben Mechanismen wie bei einfachen Kommandos). Fehlt die Wortliste, so wird die Liste der Positionsparameter verwendet (innerhalb von Shellskripten ist dies die Liste der Kommandozeilenargumente; auf der Kommandozeile selbst ist es eine vom Kommando set erzeugte Liste):

user@sonne> set a b c
user@sonne> for i; do echo $i; done
a
b
c

Mit Bash-Version 2.0.4 wurde die for--Schleife um eine an die Programmiersprache C angelehnte Syntaxvariante erweitert:

for ((Ausdruck_1; Ausdruck_2; Ausdruck_3)); do ...done

Bei den Ausdrücken handelt es sich um arithmische Substitutionen; Ausdruck_1 wird üblicherweise die Zuweisung eines Anfangswertes an eine Schleifenvariable beinhalten; Ausdruck_2 dient als Abbruchbedingung und Ausdruck_3 zum Weiterschalten der Schleifenvariablen. Ausdruck_1 wird nur einmal beim Eintritt in die Schleife ausgewertet; Ausdruck_2 wird vor jedem und Ausdruck_3 nach jedem Schleifendurchlauf neu berechnet.

user@sonne> for ((i=0; i<10; i++)); do echo -n "$i "; done
0 1 2 3 4 5 6 7 8 9

Den Einsatz dieser Form der for-Schleife sollten Sie nur in Betracht ziehen, wenn das Skript nicht portable sein muss oder sicher gestellt ist, dass auf jeder Zielmaschine die Bash in der aktuellsten Version installiert ist.

Beispiel 1: Beim Übersetzen von Softwarepaketen bricht der Vorgang häufig mit einer Fehlermeldung wie "_itoa.o(.text+0x50): undefined reference to `__umoddi3`" ab. Ursache ist die Verwendung von "__umoddi3". Vermutlich wurde vergessen, eine bestimmte Bibliothek, die das Symbol enthält, hinzuzulinken. Nun gilt es herauszufinden, welche Bibliothek notwendig ist. Die folgende Kommandozeile findet diese, sofern sie existiert:

user@sonne> for i in $(find /usr -name "*.a" 2>/dev/null); do nm $i 2>/dev/null | grep -sq "T __umoddi3" && echo $i; done
/usr/lib/gcc-lib/i486-linux/egcs-2.91.66/libgcc.a
/usr/lib/gcc-lib/i486-linux/2.7.2.3/libgcc.a
/usr/i486-glibc20-linux/lib/gcc-lib/i486-glibc20- linux/egcs-2.91.66/libgcc.a

Erläuterung: Das Beispiel beschränkt die Suche auf statische Bibliotheken unterhalb von "/usr". nm liest aus jeder Bibliothek die enthaltenen Symbole ("nm" vermag auch die Symbole von Object-Dateien lesen), mit grep suchen wir nach dem Symbol. Uns interessieren allerdings nur Bibliotheken, wo das Symbol definiert ist (und nicht solche, die es nur verwenden), deshalb die Suche nach "T __umoddi3" (Rufen Sie mal "nm /usr/lib/libc.a" auf und durchforsten die Ausgabe, um den Zweck zu verstehen).

Beispiel 2: Der Umsteiger von DOS mag die Möglichkeit vermissen, mehrere Dateien mit einer bestimmten Dateikennung zu kopieren und die Dateikennung gleichzeitig zu verändern ("copy *.bat *.bak"). Hat der Leser die Mechanismen einer Unix-Shell verstanden, sollte ihm geläufig sein, warum ein solcher Aufruf unter Unix nicht das erwartete Resultat erzielt. Mit Hilfe einer "for-Schleife" lässt sich das COMMAND.COM-Verhalten einfach simulieren:

user@sonne> for i in $(ls *.bat); do cp $i ${i%.*}.bak; done

Erläuterung: Die Generierung des Ziel-Dateinamens enthält eine Parametersubstitution.

Die while-Schleife evaluiert die ihr unmittelbar folgenden Kommandos (zwischen "while" und "; do") und führt die Kommandos des Schleifenkörpers solange aus, wie der Rückgabestatus des letzten Kommandos der Liste gleich Null ist. Die until-Schleife quittiert dagegen ihren Dienst, sobald das letzte Kommando der Liste den Wert Null zurück liefert:

while Bedingung do
    Liste von Kommandos
done

until Bedingung do
    Liste von Kommandos
done

Beispiel 1: Die nachfolgende Schleife berechnet die Werte 2n für n=1..8:

user@sonne> declare -i i=1; z=2; while [ $i -le 8 ]; do echo "2^$i=$z"; i=i+1; z=$((2*$z)); done
2^1=2
2^2=4
2^3=8
2^4=16
2^5=32
2^6=64
2^7=128
2^8=256

Beispiel 2: Die folgende while-Schleife zählt, wie viele Benutzer die Gruppe "100" als default-Gruppe besitzen:

user@sonne> (exec < /etc/passwd; IFS=":"; declare -i users=0; while read line; do set $line; test $4 -eq "100" && users=users+1 ; done; echo "Gruppe users hat $users Default-Mitglieder")

Erläuterung: Da die Standardeingabe mittels "exec" auf die Datei /etc/passwd umgeleitet wurde, ist die letzte Eingabe, die aus dieser Quelle kommt, das EndOfFile (EOF). Die Bash unter Linux ist allerdings (meist) so konfiguriert, dass EOF auch zum Beenden der Shell führt ([Ctrl][D]). Um die aktuelle Shell nicht unbeabsichtigt ins Nirwana zu delegieren, wurde die Ausführung in einer Subshell vorgenommen. Der Internal Field Separator (IFS) beinhaltet die Trennzeichen, anhand derer die Bash die Eingaben in einzelne Worte zerlegt. Normalerweise sind diese die Whitespaces; wir benötigen aber den Doppelpunkt, da jener die Felder der Passwortdatei aufteilt. Innerhalb der "while-Schleife" lesen wir jeweils eine Zeile in "line" ein (mittels read) und erzeugen eine Parameterliste (set $line). In dieser interessiert der vierte Parameter (Gruppeneintrag). Ist dieser "100", wird die Variable "users" hoch gezählt.

Näheres zu "IFS", "read" und "set" finden Sie im Abschnitt Initialisierungen.

Die Komplexität obiger Anwendungen von "while" zeigt auf, dass die Verwendung von Schleifen weniger auf der Kommandozeile verbreitet ist, sondern zu wichtigen Bestandteilen in Shellskripten zählt.

select hat mit dem klassischen Schleifenkonzept nichts gemein; sie schreibt in einer Endlosschleife eine Liste von Auswahlalternativen auf die Standardfehlerausgabe und wartet auf eine Interaktion mit dem Benutzer.

select Name [ in Liste ] do
    Liste von Kommandos
done

Das Prinzip lässt sich am einfachsten anhand eines Beispiels demonstrieren. In einem Menü sollen die Dateien des aktuellen Verzeichnisses präsentiert werden. Zur vom Benutzer ausgewählten Datei werden weitere Informationen ausgegeben; außerdem soll die Beendigung der Schleife angeboten werden:

user@sonne> select file in * Ende; do
> if test -e $file; then
> ls -l $file;
> else
> break;
> fi;
>done
1) ./index.html      5) ./help.txt       9) ./symbol9.html  13) ./pfad.gif
2) ./symbol6.html    6) ./symbol5.html  10) ./symbol2.html  14) Ende
3) ./foo.tgz         7) ./symbol3.html  11) ./symbol4.html
4) ./symbol8.html    8) ./symbol7.html  12) ./symbol1.html
#? 3
-rw-r--r--   1   user   users       3102 Aug 17 10:49 foo.tgz
1) ./index.html      5) ./help.txt       9) ./symbol9.html  13) ./pfad.gif
2) ./symbol6.html    6) ./symbol5.html  10) ./symbol2.html  14) Ende
3) ./foo.tgz         7) ./symbol3.html  11) ./symbol4.html
4) ./symbol8.html    8) ./symbol7.html  12) ./symbol1.html
#? 14
user@sonne>

Die Liste in "select" wird allen Expansionen (wie bei einfachen Kommandos) unterzogen. Diese Liste wird allerdings nur beim ersten Eintritt in die Schleife generiert. Eine Änderung dieser wird also im Menü nicht sichtbar.

Verschachtelte Schleifen: Angenommen, das letzte Beispiel sollte dahin gehend modifiziert werden, dass die erwählte Datei gelöscht werden soll. Das Problem ist nun, dass diese Datei im nachfolgenden Menüaufrufs noch immer gelistet wird. Eine Lösung wäre die Kapselung des select-Aufrufs in einer umgebenden "while-Schleife". Die "select"-Auswahl wird nun nach jedem Durchlauf verlassen, sodass das Menü erneut aufgebaut wird. Um nun beide Schleifen zu verlassen, ist die zu verlassenden Schleifentiefe dem "break"-Aufruf mitzugeben:

user@sonne> while :; do
> select file in * Ende; do
> if test -e $file; then
> rm $file;
> break;
> else
> break 2;
> fi;
> done
> done
1) ./index.html      5) ./help.txt       9) ./symbol9.html  13) ./pfad.gif
2) ./symbol6.html    6) ./symbol5.html  10) ./symbol2.html  14) Ende
3) ./foo.tgz         7) ./symbol3.html  11) ./symbol4.html
4) ./symbol8.html    8) ./symbol7.html  12) ./symbol1.html
#? 3
1) ./index.html      5) ./symbol5.html   9) ./symbol2.html  13) Ende
2) ./symbol6.html    6) ./symbol3.html  10) ./symbol4.html
3) ./symbol8.html    7) ./symbol7.html  11) ./symbol1.html
4) ./symbol8.html    8) ./symbol9.html  12) ./pfad.gif
#? 13
user@sonne>

Expansionen der Bash Zurück Anfang Weiter

Im Abschnitt zur Syntax der Bash tauchte mehrfach der Begriff der Expansion auf. Im Sinne der Bash umfasst Expansion eine Menge von Regeln, nach denen die Eingabe in der Kommandozeile zunächst bearbeitet wird. Bestimmte Zeichenmuster werden hierbei durch andere substituiert - sie "expandieren".

Welche Regeln wann und in welcher Reihenfolge zum Einsatz gelangen, hängt vom konkreten Kontext ab und wurde im Zusammenhang mit einfachen Kommandos (Expansion der Worte) und Variablenzuweisungen genannt. An dieser Stelle soll nun die fehlende Beschreibung der einzelnen Expansionsmechanismen nachgeholt werden, wobei die Reihenfolge der Darstellung einzig der alphabetischen Anordnung entspricht!

Arithmetik

Die Bash ist kein Taschenrechner. Dennoch besitzt sie ein erstaunliches Potenzial an eingebauten Rechenoperationen, die -- nach Prioritäten geordnet -- nachfolgende Tabelle zusammenfasst:

+  -

Einstelliger Operator (Vorzeichen)

!  ~

Logische und bitweise Negation

**

Exponentialfunktion

*  /  %

Multiplikation, Division und Modulo-Operator

+  -

Addition, Subtraktion

<<  >>

Bitweise Links-/Rechtsverschiebung

<=  >=  <  >

Vergleiche

==  !=

Gleichheit und Ungleichheit

&

Bitweises UND

^

Bitweises Exclusive ODER

|

Bitweises ODER

&&

Logisches UND

||

Logisches ODER

expr ? expr : expr

Bedingte Zuweisung

=, *=, /=, %=, +=, -= <<=, >>=, &=, ^=, |=

Zuweisungen

Als Operanden sind Konstanten und Shellvariablen (deren Inhalt als long integer betrachtet wird) erlaubt. Beginnt eine Konstante mit "0", dann wird sie als oktale Zahl verstanden; steht "0x" am Anfang, handelt es sich um eine hexadezimale Konstante.

Konstanten können zu jeder Basis zwischen 2 und 64 angegeben werden, so kann die Zahl 63 u.a. wie folgt dargestellt werden:

  • Zur Basis 10: 10#63
  • Zur Basis 8: 8#77
  • Zur Basis 16: 16#3f


Die so genannte arithmetische Substitution ist der gebräuchliche Weg, um Berechnungen durchzuführen:

  • Bash Versionen <2: Der zu berechnende Ausdruck wird in eckigen Klammern geschrieben: $[...]
  • Bash ab Version 2: Der zu berechnende Ausdruck wird in doppelte runde Klammern geschrieben: $((...)) (die alte Syntax wird weiterhin unterstützt)

Einige Beispiele sollen die Anwendung verdeutlichen:

user@sonne> b=5; b=$((b+1)); echo $b
6
user@sonne> a=$((b+=10)); echo $a
16
user@sonne> echo $((a>b?1:0))
1
user@sonne> echo $((8#17**2))
225
user@sonne> echo $((017**2))
225
user@sonne> echo $((-0x64*3#11%6))
-4
user@sonne> echo $((4<<1))
8

Wird als Operand eine Variable benutzt, so wird versucht, deren Inhalt in eine Ganzzahl zu konvertieren. Enthält die Variable keine Zahl, wird der Inhalt zu "0" konvertiert:

user@sonne> b="Ist b keine Zahl, wird b zu 0 konvertiert"
user@sonne> echo $b
Ist b keine Zahl, wird b zu 0 konvertiert
user@sonne> b=$(($b+1)); echo $b
1

Klammernexpansion

Mit Hilfe der Klammererweiterung lassen sich beliebige Zeichenketten generieren. Im einfachsten Fall verwendet man eine Präfix-Zeichenkette, gefolgt von beliebig vielen, mit geschweiften Klammern umschlossenen und durch Kommas getrennten Zeichen(ketten), wiederum gefolgt von einer optionalen Postfix-Zeichenkette. Das Ergebnis sind nun Zeichenketten der Art "PräfixZeichenkette_1_Postfix", "PräfixZeichenkette_2_Postfix", ..., "PräfixZeichenkette_n_Postfix".

An einem Beispiel lässt sich das Prinzip leicht verdeutlichen:

user@sonne> echo Beispiel{_1_,_2_,_3_}
Beispiel_1_ Beispiel_2_ Beispiel_3_


Präfix und Postfix können ihrerseits wiederum Klammererweiterungen sein und Klammererweiterungen lassen sich verschachteln, so dass sich z.B. mit nur einem Befehl eine ganze Verzeichnishierarchie erzeugen lässt:

user@sonne> mkdir -p bsp/{ucb/{ex,edit},lib/{bla,foo}}
user@sonne> du bsp | cut -b 3-
bsp/ucb/ex
bsp/ucb/edit
bsp/ucb
bsp/lib/bla
bsp/lib/foo
bsp/lib
bsp

Kommandosubstitution

Die Kommandosubstitution erlaubt das Ersetzen ihres Aufrufes durch ihre Ausgabe. Es existieren zwei Syntaxvarianten des Aufrufs:

$(Kommando)
`Kommando`

Die Bash führt das Kommando aus und ersetzt seinen Aufruf auf der Kommandozeile durch dessen Ausgabe, wobei abschließende Zeilenendezeichen entfernt wurden.

# ohne Kommandosubstitution user@sonne> find / -name "whatis" 2>/dev/null | ls -l | head -5
insgesamt 15888
-rw-r--r--    1 user    users    787067 Apr  1 09:02 Buch.tar.gz
drwxr-xr-x    4 user    users      4096 Jan 16 19:49 Dhtml
drwx------    5 user    users      4096 Apr 26 09:48 Desktop
drwxr-xr-x    4 user    users      4096 Apr 21 08:43 IGLinux

# mit Kommandosubstitution
user@sonne> ls -l $(find / -name "whatis" 2>/dev/null)
ls -l $(find / -name "whatis"  2>/dev/null)
-rw-r--r--    1 root     root     94414 Jun 13 18:34 /usr/X11R6/man/whatis
-rw-r--r--    1 root     root    792270 Jun 13 18:34 /usr/man/allman/whatis
-rw-r--r--    1 root     root    220874 Jun 13 18:34 /usr/man/whatis
-rw-r--r--    1 root     root         0 Jun 13 18:34 /usr/openwin/man/whatis

Eine solche Kommandosubstitution kann auch bei der Zuweisung an eine Variable angewendet werden:

user@sonne> AktuellPfad=$(pwd)
user@sonne> echo $AktuellerPfad
/home/user

Parameter- und Variablenexpansion

Folgt einem Dollarzeichen $ ein Variablenname oder eine öffnende geschweifte Klammer ${...}, so spricht man von einer Variablen- bzw. Parameterexpansion. Die geschweiften Klammern dienen zur Gruppierung und sind bei skalaren Variablen, die nicht per Parameterexpansion behandelt werden sollen, nicht notwendig.

Beginnen wir mit einem Beispiel der Expansion einer skalaren Variable ohne Parameterexpansion:

user@sonne> var=user
user@sonne> var2=~$var
user@sonne> echo $var2
~user
user@sonne> eval echo $var2
/home/user

Bemerkung: Das Beispiel verdeutlicht die Reihenfolge der Auflösung bei Zuweisung eines Wertes an "var2". Im ersten Schritt ist die Tilde nicht auflösbar, deshalb geht sie unverändert in "var2" ein. In einem zweiten Schritt expandiert dann der Inhalt von "var", so dass "var2" nun "~user" beinhaltet. Um den Expansionsmechnismus zu demonstrieren, wurde eine erneute Bewertung von "var2" erzwungen (eval); nun expandiert "~user" zum Heimatverzeichnis "/home/user".

Ist das erste Zeichen eines Parameters das Ausrufezeichen, so handelt es sich um eine indirekte Expansion. Die Bash ersetzt den Ausdruck nun nicht mehr durch den Inhalt der Variablen, sondern betrachtet den Inhalt als den Namen einer Variablen, zu deren Inhalt nun expandiert wird. Ein Beispiel erklärt den Sachverhalt wohl deutlicher, als es Worte vermögen:

user@sonne> var=user
user@sonne> var2=var
user@sonne> echo $var2
var
user@sonne> echo ${!var2}
user

Die weiteren Mechanismen zur Parameterexpansion manipulieren den Inhalt von Variablen. Die Beispiele werden zeigen, dass diese Form der Substitution vor allem für die Shellprogrammierung von immensem Nutzen ist und genau dort werden sie uns wieder begegnen. »parameter« bezeichnet nachfolgend den Variablennamen und »word« steht entweder für eine Zeichenkette oder für eine Variable, die selbst wieder eine Parameter-, Kommando, Tildeexpansion oder eine arithmetische Berechnung beinhalten kann.

${parameter:-word}
Default-Wert setzen: falls »parameter« nicht gesetzt ist, liefert die Expansion »word« zurück, ansonsten den Inhalt von »parameter«:

user@sonne> var=5
user@sonne> echo ${var:-test}
5
user@sonne> unset var; echo ${var:-test}
test
user@sonne> unset var; echo ${var:-${test}leer}
leer
${parameter:=word}
Default-Wert zuweisen: Das Konstrukt liefert immer den Wert »word« und weist diesen »parameter« zu, falls diese Variable leer war:

user@sonne> var=5
user@sonne> echo ${var:=test}
5
user@sonne> echo $var
5
user@sonne> unset var; echo ${var:=test}
test
user@sonne> echo $var
test
${parameter:?word}
Inhalt oder Fehlermeldung: Wenn »parameter« gesetzt ist, wird der Inhalt der Variablen geliefert, ansonsten »word« (das meist eine Fehlermitteilung enthalten wird), wobei der »parameter« nicht erzeugt und das Skript abgebrochen wird. Ist »word« leer, wird eine Standard-Fehlermeldung generiert:

user@sonne> var=5
user@sonne> echo ${var:?}
5
user@sonne> unset var; echo ${var:?}
bash: var: parameter null or not set
user@sonne> echo ${var:?Das Programm $0 kennt diese Variable nicht\!}
bash: var: Das Programm -bash kennt diese Variable nicht!
${parameter:+word}
Alternativer Wert: Falls "parameter" gesetzt ist, wird die Variable gnadenlos mit "word" überschrieben. Ist sie nicht gesetzt, bleibt sie auch weiterhin nicht gesetzt. Rückgabewert ist der Inhalt von "parameter" (also entweder "word" oder nichts):

user@sonne> var=5
user@sonne> echo ${var:+Die PID des aktuellen Prozesses ist: $$}
Die PID des aktuellen Prozesses ist: 438
user@sonne> unset var; echo ${var:+$$}

${parameter:offset}
${parameter:offset:length}
Teilzeichenkette extrahieren: Beginnend an der durch »offset« angegebenen Position werden maximal »length« Zeichen der Variable »parameter« entnommen. Fehlt die Längenangabe, so werden alle Zeichen ab »offset« bis zum Ende von »parameter« geliefert. Fehlt »offset«, beginnt die Substitution am Anfang der Variable. »offset« und »length« können ganze Zahlen oder arithmetische Berechnungen sein; »offset« kann zu einer negativen Zahl expandieren, dann beginnt die Substitution ausgehend vom Ende der Variable:

user@sonne> bsp="Offset=Anfang und Length=Anzahl"
user@sonne> echo ${bsp:18}
Length=Anzahl
user@sonne> echo ${bsp::13}
Offset=Anfang
user@sonne> echo ${bsp:18:6}
Length
user@sonne> echo ${bsp:$((-13)):$[12-6]}
Length
${#parameter}
Anzahl Zeichen: Das Konstrukt liefert die Anzahl Zeichen, die in"parameter" enthalten sind:

user@sonne> bsp="Nicht erwähnt wurde die Verwendung von '*' und '@' als parameter"
user@sonne> echo ${#bsp}
64
${parameter#word}
${parameter##word}
Muster am Anfang: Der Anfang von "parameter" wird mit dem Muster "word" verglichen. Stimmen beide überein, wird alles nach dem Muster geliefert, sonst der Inhalt von "parameter". Wird nur ein "#" verwendet, wird die kürzest mögliche Expansion des Musters betrachtet, mit "##" die längst mögliche. Ist "parameter" ein Feld (* oder @), so wird die Substitution für jedes einzelne Element vorgenommen. "parameter" bleibt immer unverändert:

user@sonne> manpath="/usr/man/man1/bash.1.gz"
user@sonne> echo ${manpath#*/}
usr/man/man1/bash.1.gz
user@sonne> echo ${manpath##*/}
bash.1.gz
${parameter%word}
${parameter%%word}
Muster am Ende: Die Substitution arbeitet analog zu der im vorigen Punkt beschriebenen, nur dass nun der Mustervergleich am Ende des Inhalts von "parameter" beginnt. Geliefert wird jeweils die Teilzeichenkette vor dem Muster:

user@sonne> manpath="/usr/man/man1/bash.1.gz"
user@sonne> echo ${manpath%/*}
usr/man/man1
user@sonne> echo ${manpath%%.*}
/usr/man/man1/bash
${parameter/pattern/string}
${parameter//pattern/string
}
Ersetzen eines Musters: Mit den Substitutionen lassen sich Muster "pattern" im Inhalt einer Variablen "parameter" durch "string" ersetzen. In der ersten Form wird nur ein Auftreten (von vorn), in der zweiten werden alle Auftreten des Musters ersetzt. Beginnt "pattern" mit einem #, so muss das Muster mit dem Anfang von "parameter" übereinstimmen, beginnt es mit "%", muss es mit dem Ende übereinstimmen. Ist "string" leer, wird "pattern" aus "parameter" gelöscht:

user@sonne> manpath="/usr/man/man1/bash.1.gz"
user@sonne> echo ${manpath/man/}
/usr//man1/bash.1.gz
user@sonne> echo ${manpath//man/}
/usr//1/bash.1.gz
user@sonne> echo ${manpath/#*\//}
bash.1.gz
user@sonne> echo ${manpath/man/local/man}
/usr/local/man/man1/bash.1.gz

Pfadnamensexpansion

Bei dem als Pfadnamensubstitution bekannten Mechanismus durchsucht die Bash die Kommandozeile nach den Metazeichen *, ?, ( und [. Jedes, eines dieser Zeichen enthaltende Token wird als Muster eines Dateinamens interpretiert und durch die alphabetisch sortierte Liste der übereinstimmenden Dateinamen ersetzt. Expandiert ein solches Token nicht, erscheint es unverändert auf der Kommandozeile, falls die Shelloption nullglob nicht gesetzt ist. Anderenfalls wird das Token gelöscht.

Die Metazeichen bedeuten im Einzelnen:

*

Beliebig viele beliebige Zeichen (auch 0)

?

Genau ein beliebiges Zeichen

[...]

Im einfachsten Fall steht es für genau ein Zeichen aus der Menge (bspw. "[aeiou]" für einen Vokal).
Diese Angabe kann negiert werden ("alle außer diese Zeichen"), indem das erste Zeichen nach [ ein ! oder ^ ist ("[!abc]" bzw. "[^abc]").
Anstatt einzelne Zeichen aufzuzählen, lassen sich Bereiche angeben ("[a-z]" meint alle Kleinbuchstaben).
Und letztlich können die Zeichenklassen alnum alpha ascii blank cntrl digit graph lower print punct space upper xdigit verwendet werden ("[:digit:]").

Zeichen(...)

Die in den Klammern eingeschlossenen Muster werden nur betrachtet, wenn die Shelloption extglob gesetzt ist. Die Muster sind eine Liste von Zeichenketten, die durch | getrennt sind. Das Zeichen vor der öffnenden Klammer reguliert die Auswertung des Musters:

?(Muster-Liste) Kein oder ein Auftreten eines Musters
*(Muster-Liste) Kein oder mehrere Auftreten eines Musters
+(Muster-Liste) Ein oder mehrere Auftreten eines Musters
@(Muster-Liste) Genau ein Auftreten eines Musters
!(Muster-Liste) Alle außer den angegebenen Mustern

Prozesssubstitution

Die Ein- bzw. Ausgabe von Prozessen kann mittels der Prozesssubstitution mit einer FIFO-Datei verbunden werden.

Taucht ein Konstrukt der Art <( Liste ) bzw. >( Liste ) auf, werden die durch Liste benannten Kommandos in einer Subshell gestartet. Gleichzeitig wird die Ausgabe (>( ... )) bzw. Eingabe (<( ... )) der Kommandos mit einer automatisch erzeugten FIFO-Datei verbunden. Auf der Kommandozeile erscheint nach erfolgter Substitution der Name der erzeugten FIFO-Datei.

user@sonne> ls <(echo "hello")
/dev/fd/63

Mit Hilfe der Prozesssubstitution könnte man den vi dazu bewegen, die Ausgaben eines Kommandos zu lesen:

user@sonne> vi <(ls /boot/vm*)
/boot/vmlinuz
/boot/vmlinuz.old
~

~
"/dev/fd/63" [fifo/socket] 2L, 32C                                        1,1            All

Ein weiteres Beispiel dient zur Bestandsaufnahme laufender Prozesse:

user@sonne> diff <(ps ax) <(sleep 10; ps ax)
64d63
<  2129 pts/0    S      0:00 /bin/bash
67,68c66
<  2132 pts/0    R      0:00 ps ax
<  2133 pts/0    S      0:00 sleep 10
---
>  2134 pts/1    S      0:00 top
>  2135 pts/0    R      0:00 ps ax

Im Beispiel ist der Prozess top neu hinzugekommen, dass die Aufrufe der Kommandos ps und sleep erscheinen, war zu erwarten.

Und abschließend vergleichen wir die Inhalte zweier Archive:

user@sonne> diff <(tar tzf Buch1.tar.gz) <(tar tzf Buch.tar.gz)
325a326,328
> Images/tkinfo.gif
> Images/GlobaleVariable.gif
> Images/LokaleVariable.gif

Innerhalb der Klammern >( ... ), <( ... ) können Parameter- Kommando- sowie arithmetische Substitutionen benutzt werden.

Quoting

Das Quoting wird verwendet, um die Interpretation von Sonderzeichen durch die Bash zu verhindern. Die Sonderzeichen sind ;, &, ( ), { }, [ ], |, >, <, Zeilenumbruch, Tabulator, Leerzeichen, $, ? und *.

Ein einzelnes Sonderzeichen wird am einfachsten durch einen vorangestellten Backslash "gequotet". Die Bash betrachtet die Kombination aus Backslash und dem nachfolgenden Zeichen als ein einzelnes Zeichen, versucht aber nicht, dieses zu expandieren.

Um mehrere Zeichen vor der Interpretation zu schützen, können diese zwischen zwei Anführungsstrichen (doppelt/einfach) eingeschlossen werden. Während die einfachen Anführungsstriche die Interpretation aller eingeschlossenen Sonderzeichen verhindern, erlauben doppelte Anführungsstriche die Substitution von Variablen und Kommandos.

Ein kurzes Beispiel demonstriert den Sachverhalt:

user@sonne> echo "Das Arbeitsverzeichnis der $SHELL ist $(pwd)"
Das Arbeitsverzeichnis der /bin/bash ist /home/user

user@sonne> echo 'Das Arbeitsverzeichnis der $SHELL ist $(pwd)'
Das Arbeitsverzeichnis der $SHELL ist $(pwd)

user@sonne> echo Das Arbeitsverzeichnis der \$SHELL ist $(pwd)
Das Arbeitsverzeichnis der $SHELL ist /home/user

Tildeexpansion

Beginnt der Wert mit einer ungequoteten Tilde (~), wird versucht, diese zu substituieren. Betrachtet werden alle der Tilde folgenden Zeichen bis zum ersten Schrägstrich (Slash). Ergibt dies eine gültige Benutzerkennung, so expandiert der Ausdruck zum Heimatverzeichnis dieses Benutzers. Folgt der Tilde unmittelbar der Schrägstrich, wird der Ausdruck durch den Inhalt der Variablen HOME ersetzt; ist diese nicht gesetzt, wird das Heimatverzeichnis des aktuellen Benutzers angenommen:

user@sonne> var=~
user@sonne> echo $var
/home/user
user@sonne> var=~root/
user@sonne> echo $var
/root/

Wortzerlegung

In diesem Schritt wird die Kommandozeile in so genannte Token unterteilt. Welche Zeichen als Separatoren verwendet werden, verrät die Variable $IFS (Internal Field Separator). Ist diese nicht gesetzt, gelten die schon erwähnten Whitespaces als Begrenzer, sofern sie nicht innerhalb von (Doppel) Anführungsstrichen stehen oder durch den Backslash "gequotet" wurden.

Initialisierung Zurück Anfang Weiter

Die Initialisierungsdateien

Handelt es sich bei einer Login-Shell um die Bash oder die Korn Shell, sucht diese als erstes nach der Datei /etc/profile. Diese wird vom Systemadministrator meist dazu genutzt, allen Nutzern eine auf das System zugeschnittene Umgebung zu gewähren. Häufig enthalten sind:

  • Prüfen der Mailbox auf neue Nachrichten
  • Ausgabe der "Nachricht des Tages" (Inhalt der Datei /etc/motd)
  • Setzen wichtiger Aliasse
  • Vorbelegen wichtiger Variablen

Gerade in Umgebungen, wo die Benutzer sowohl auf die Bash als auch auf die Ksh zurück greifen, sollte die /etc/profile sorgfältig erstellt werden, da gewisse Unterschiede zu ungewollten Effekten führen können.

Zwei weitere Konfigurationsdateien sollen rein Bash-spezifische Einstellungen ermöglichen. Die Dateien .bash_profile und .bash_login im Heimatverzeichnis eines Nutzers werden, falls sie existieren, in beschriebener Reihenfolge behandelt. Um kompatibel zur kommerziellen Unix-Shell sh zu sein, liest die Bash die beiden Dateien allerdings nicht aus, wenn auf sie unter dem Namen sh (ein Link auf /bin/bash) zugegriffen wird.

Anschließend wertet die Bash noch eine eventuell vorhandene Datei .profile aus, die, wiederum im Heimatverzeichnis eines Nutzers gelegen, erster Ansatzpunkt für den Nutzer darstellt, persönliche Einstellungen zur Umgebung vorzunehmen.

Im Falle einer interaktiven, Nicht-Login Shell versucht die Bash Befehle aus einer Datei ~/.bashrc abzuarbeiten. Schließlich hällt eine nicht-interaktive Bash (Shellskripte) nach einer in der Umgebungsvariablen BASH_ENV gespeicherten Datei Ausschau.

In allen erwähnten Dateien können dieselben Befehle stehen (z.B. Kommandos, Alias- und Variablendefinitionen, ...). Für gewöhnlich werden die Benutzer bestenfalls eine existierende Datei nach ihren Bedürfnissen anpassen, anstatt sich das notwendige Wissen anzueignen, um eine Konfigurationsdatei vollständig zu erstellen. Die nachfolgend, reichlich kommentierte Datei kann als Ausgangspunkt für eigene Experimente dienen:

user@sonne> cat /etc/profile
# /etc/profile
#

umask 022

# Abstürzende Programme sollen keine Speicherdumps anlegen
ulimit -Sc 0
# Ein einzelner Benutzer darf maximal 128 Prozesse gleichzeitig starten
ulimit -u 128
# Kein Limit für das Datensegment eines Prozesses
ulimit -d unlimited
# Die Kornshell kennt die folgende Option nicht (Stackgröße)
test -z "$KSH_VERSION" && ulimit -s unlimited

# Die PATH-Variable wird mit Standardpfaden belegt
MACHINE=`test -x /bin/uname && /bin/uname --machine`
PATH=/usr/local/bin:/usr/bin:/usr/X11R6/bin:/bin
for DIR in ~/bin/$MACHINE ~/bin ; do
    test -d $DIR && PATH=$DIR:$PATH
done

# PATH für root wird um sbin-Verzeichnisse erweitert
test "$UID" = 0 && PATH=/sbin:/usr/sbin:/usr/local/sbin:$PATH

# PATH wird um Binary-Pfade ergänzt:
for DIR in /usr/lib/java/bin \
           /usr/games/bin \
           /usr/games \
           /opt/bin \
           /opt/gnome/bin \
           /opt/kde/bin; do
    test -d $DIR && PATH=$PATH:$DIR
done
export PATH

# Umgebungsvariablen, die verbreitete Programme verwenden, werden belegt:
if test -n "$TEXINPUTS" ; then
    TEXINPUTS=":$TEXINPUTS:~/.TeX:/usr/doc/.TeX"
else
    TEXINPUTS=":~/.TeX:/usr/doc/.TeX"
fi
export TEXINPUTS

PRINTER='lp'
export PRINTER

LESSCHARSET=latin1
export LESSCHARSET
LESS="-M -S -I"
export LESS
LESSKEY=/etc/lesskey.bin
export LESSKEY
LESSOPEN="|lesspipe.sh %s"
export LESSOPEN

# Suchpfade nach Manuals werden in MANPATH aufgenommen:
MANPATH=/usr/local/man:/usr/share/man:/usr/man:/usr/X11R6/man
for DIR in /usr/openwin/man \
           /usr/share/man/allman \
           /usr/man/allman \
           /usr/man/de ; do
    test -d $DIR && MANPATH=$MANPATH:$DIR
done
export MANPATH

# Suchpfade für die Info-Seiten:
INFOPATH=/usr/local/info:/usr/share/info:/usr/info
export INFOPATH

# Rechnername und Newsserver
HOSTNAME="`hostname`"
export HOSTNAME
NNTPSERVER=`cat /etc/nntpserver 2> /dev/null`
export NNTPSERVER

# Damit die Ausgabe von "ls" farbig ist...
if [ -x /usr/bin/dircolors ] ; then
    if test -f ~/.dir_colors ; then
        eval `dircolors -b ~/.dir_colors`
    elif test -f /etc/DIR_COLORS ; then
        eval `dircolors -b /etc/DIR_COLORS`
        fi
fi

unalias ls 2>/dev/null

if test "$UID" = 0 ; then
    LS_OPTIONS='-a -N --color=tty -T 0';
else
    LS_OPTIONS='-N --color=tty -T 0';
fi
export LS_OPTIONS

# Einige Aliasdefinitionen (gekürzt)
alias ls='ls $LS_OPTIONS'
alias dir='ls -l'
alias ll='ls -l'
alias o='less'
alias ..='cd ..'
alias ...='cd ../..'
alias rd=rmdir
alias md='mkdir -p'
alias unix2dos='recode lat1..ibmpc'
alias dos2unix='recode ibmpc..lat1'
alias which='type -p'

# Einige Funktionen
function startx { /usr/X11R6/bin/startx $* 2>&1 | tee ~/.X.err ; }
function remount { /bin/mount -o remount,$* ; }
# Das Prompt wird für Root und normale Benutzer unterschiedlich gesetzt:
if [ "$SHELL" = "/usr/bin/pdksh" -o "$SHELL" = "/usr/bin/ksh" -o "$SHELL" = "/bin/ksh" -o "$0" = "ksh" ]; then
    PS1="! $ "
elif [ -n "$ZSH_VERSION" ]; then
    if test "$UID" = 0; then
    PS1='%n@%m:%~ # '
    else
    PS1='%n@%m:%~ > '
    fi
elif [ -n "$BASH_VERSION" ] ; then
    set -p
 if test "$UID" = 0 ; then
        PS1="\h:\w # "
    else
        PS1="\u@\h:\w > "
    fi
else
    PS1='\h:\w \$ '
fi
PS2='> '
export PS1 PS2

unset DIR

Abhängig von der konkreten Linuxdistribution werden ggf. aus den beschriebenen Standarddateien heraus weitere Startup-Dateien eingelesen.

Die Shellvariablen

Variablen lassen sich auf verschiedene Art und Weise in konkrete Kategorien einordnen. Im Sinne der Shell beeinflussen sie deren Verhalten. Diese, in dem Zusammenhang als Umgebungsvariablen bezeichneten Variablen unterteilen sich wiederum in Variablen, die die Bash setzt und solche, die sie nur benutzt.

Wichtige Variablen, die die Bash setzt, sind (Auswahl):

PPID

Prozessnummer des Bash-Vorgänger-Prozesses

PWD

Aktuelles Arbeitsverzeichnis.

OLDPWD

Vorhergehendes Arbeitsverzeichnis; bspw. könnten Sie mit "cd -" in dieses wechseln.

REPLY

Variable, die die zuletzt vom Kommando read gelesene Zeile enthält.

BASH

Vollständiger Pfad der aktuellen Bash. In dem Zusammenhang gibt BASH_VERSION die Programmversion preis und BASH_VERSINFO ist ein Feld (5 Einträge) mit genaueren Angaben

RANDOM

Erzeugt eine Zufallszahl bei jedem Zugriff.

OPTARG und OPTIND

Wert und Index des letzten, von getopts bearbeiteten Argument.

HOSTNAME

Rechnername ohne Domain.

HOSTTYPE

Architekturtyp des Rechners

OSTYPE

Typ des Betriebssystems.

SHELLOPTS

Liste der Shelloptionen.

Wichtige Variablen, die die Bash benutzt, sind (Auswahl):

IFS

Der Internal Field Separator enthält die Trennzeichen, anhand derer die Bash nach erfolgter Expansion die Eingabe in Worte zerlegt.

PATH

Enthält die Suchpfade, in denen die Bash nach Kommandos sucht. Beachten Sie die Reihenfolge der Suche - bevor die Bash PATH betrachtet, durchsucht sie zunächst die Aliasse, die Funktionen und die eingebauten Kommandos nach dem Namen.

HOME

Enthält das Heimverzeichnis des Benutzers.

BASH_ENV

Enthält eine Initialisierungsdatei, die beim Start von Subshells für Skripte aufgerufen wird.

MAIL

Enthält den Namen einer Mailbox; wenn dort neue Nachrichten eintreffen, wird der Benutzer benachrichtigt. Allerdings nur, falls MAILPATH nicht gesetzt ist. Letztere Variable enthält die Pfade zu mehreren Dateien, die überwacht werden sollen.

PS1...PS2

HISTSIZE

Anzahl maximal gespeicherter Kommandos im History-Speicher

HISTFILE

Datei, wo die History bei Ende einer interaktiven Bash gespeichert wird. Wie viele Zeilen diese enthalten kann, steht in HISTFILESIZE.

TMOUT

Ist dieser Wert gesetzt, beendet sich die Bash, wenn innerhalb der angegebenen Zeitspanne (in Sekunden) keine Eingabe erfolgte. Hiermit kann auf einfache Art und Weise ein Auto-Logout realisiert werden.

Variablen besitzen einen Sichtbarkeitsbereich und lassen sich in lokale und globale Variablen unterteilen. Lokale Variablen sind nur in der Shell ihrer Definition sichtbar, d.h. in keinem innerhalb der Shell gestarteten Kommando (Ausnahme: builtin-Kommando, aber dieses "ist" ja die Shell selbst). Plausibler vorzustellen ist der Sachverhalt innerhalb der Prozesshierarchie. Eine lokale Variable ist somit nur im Prozess ihrer Definition sichtbar, nicht jedoch in den daraus abgeleiteten Prozessen. Im Gegensatz hierzu ist eine globale Variable ab dem Prozess ihrer Definition sichtbar; also in allen abgeleiteten, nicht jedoch in den übergeordneten Prozessen.

# Definition einer lokalen Variable:
user@sonne> localvar="eine lokale Variable"
# Lokale Variablen sind nur in der Shell ihrer Definition sichtbar:
user@sonne> bash
user@sonne> echo $localvar

user@sonne> exit
user@sonne> echo $localvar
eine lokale Variable
# Jede Variable kann zu einer globalen Variable erklärt werden:
user@sonne> export $localvar
# Globale Variablen sind ab (aber nicht in einer Vorgängershell) der Shell ihrer Definition sichtbar:
user@sonne> bash
user@sonne> echo $localvar

eine lokale Variable

Eine letzte Herangehensweise in die Thematik der Variablen betrachtet deren Eigenschaften, ob sie bspw. änderbar sind oder einen bestimmten Typ besitzen (vergleiche Variablen).

Spezielle Shellvariablen

Neben den soeben vorgestellten Variablen verfügt die Bash über zwei weitere Arten. Im Sprachgebrauch der Bash benennt man diese Positions- und spezielle Parameter. Beiden ist eigen, dass man ihre Werte zwar auslesen kann, aber die unmittelbare Zuweisung derselben nicht möglich ist.

Die meisten Programme sind über Kommandozeilenoptionen steuerbar, so auch die Bash. Die Positionsparameter dienen nun dazu, auf diese Optionen zuzugreifen. Die Nummerierung erfolgt gemäß der Reihenfolge der Angabe der Parameter (die Token der Kommandozeile); die Zählung beginnt bei 0. Und da i.d.R. als erstes der Kommandoname in der Eingabe erscheint, verbirgt sich dieser hinter der Variable $0:

user@sonne> echo $0
/bin/bash

Wer sich so seine Gedanken über den Sinn der Positionsparameter macht, wird schnell auf deren Verwendung in Shellskripten schließen. Und genau dort werden wir intensiven Gebrauch davon machen. Wer weiter den Hirnschmalz strapaziert, wird sich fragen, wie man die Anzahl der Positionsparameter ermittelt. Auch diese verrät eine spezielle Variable $#; der Parameter $0 wird hierbei nicht mit gezählt. Die eigentlichen Positionsparameter verbergen sich in den Variablen $1,..., $9.

Ein kurzes, nicht gerade geistreiches Beispiel soll die Verwendung andeuten; zum Erzeugen einer Parameterliste bemühen wir das eingebaute Kommando set:

# Parameterliste mit den Werten 1, 2, ...,9 erzeugen
user@sonne> set `seq 1 9`
user@sonne> echo $#
9
# Summe über die Positionsparameter berechnen
user@sonne> i=1;summe=0
user@sonne> while [ "$i" -le "$#" ]; do
>summe=$(($summe+$i));
>i=$(($i+1))
>done
user@sonne> echo $summe
45

Sie verstehen das Beispiel nicht? Dann schauen Sie sich bitte nochmals die Abschnitte zur Kommandosubstitution, zu Schleifen und Berechnungen an.

Der Denker erkennt ein Problem: "Kann ich nur 9 Parameter an ein Shellskript übergeben?" Nein! Aber der Zugriff über die Positionsparameter ist nur auf die ersten 9 Einträge möglich. Benötige ich mehr, hilft das eingebaute Kommando shift weiter, das bei jedem Aufruf die Positionsparameterliste um eine (oder die angegebene Anzahl) Position(en) nach rechts verschiebt, d.h. nach dem Aufruf von "shift" steht in "$1" der Wert, der vor dem Aufruf in "$2" stand; in "$2" steht der aus "$3" usw. Der ursprüngliche Inhalt von "$1" ist ein für allemal verloren, es sei denn, man hat ihn sich in einer Variablen gemerkt. Wir kommen nochmals auf das letzte Beispiel zurück und berechnen nun die Summe der Zahlen 1, 2, ..., 100:

user@sonne> set `seq 1 100`
user@sonne> echo $#
100
# Summe über die Positionsparameter berechnen
user@sonne> i=1;summe=0
user@sonne> while [ "$#" -ne "0" ]; do
>summe=$(($summe+$i));
>i=$(($i+1))
>shift
>done
user@sonne> echo $summe
5050

Im Zusammenhang mit den Positionsparametern stehen $* und $@, die auf den ersten Blick genau das gleiche Ergebnis erzielen, nämlich die gesamte Parameterliste zu expandieren, wobei die einzelnen Einträge jeweils durch den ersten Feldtrenner aus der Variablen IFS abgegrenzt sind. Der erste Parameter "$*" liefert dabei genau ein Wort zurück, während der zweite jeden Eintrag als eigenes Wort erzeugt.

Das Ganze klingt mächtig verworren, aber zwei Anwendungsgebiete offenbaren den eigentlichen Nutzen. Zum einen wünscht man, eine Kommandozeile nach einem bestimmten Muster zu scannen. Jeden Parameter in einer Schleife einzeln zu durchleuchten, ist der Pfad des Sisyphus; hingegen den Inhalt von "$*" bzw. "$@" zu durchmustern, das elegante Vorgehen des aufmerksamen Lesers:

user@sonne> set -- -o bla -x foo -z mir fällt nix Gescheites ein
# Ist die Option "-o" gesetzt?
# Beachten Sie das Quoten des Suchmusters!

user@sonne> echo $* | grep -q \\\-o && echo "ja!"
ja!

Die zweite (mir bekannte) Anwendung ist bei der Speicherung der Positionsparameter in einer Feldvariablen. Somit kann ohne Verwendung von shift auf alle Positionen zugegriffen werden.

user@sonne> set `seq 1 20`
user@sonne> feld=($*)
user@sonne> echo ${feld[0]} ${feld[19]}
1 20

Da IFS als erstes das Leerzeichen enthielt, »zerfällt« »$*« in einzelne Token und verhält sich analog zu »$@«. Quotet man beide Parameter in doppelte Anführungsstriche, offenbart sich der kleine aber feine Unterschied:

user@sonne> set `seq 1 20`
user@sonne> feld=($@)
user@sonne> echo ${feld[0]} ${feld[19]}
1 20
user@sonne> feld=("$@")
user@sonne> echo ${feld[0]}
1
user@sonne> feld=("$*")
user@sonne> echo ${feld[0]}
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20

Die speziellen Parameter lassen sich nicht so säuberlich in eine Schublade pressen - zu unterschiedlich sind die ihnen angedachten Aufgaben:

$?

Diese Variable beinhaltet den Rückgabewert des zuletzt beendeten Vordergrundprozesses:

user@sonne> touch /ect/passwd > /dev/null
user@sonne> echo $?
1

$!

Enthält die Prozessnummer PID des zuletzt im Hintergrund gestarteten (und noch immer aktiven) Prozesses:

user@sonne> sleep 100&
[1] 1324
user@sonne> echo $!
1324

$-

Beinhaltet alle durch das eingebaute Kommando gesetzten Shellvariablen (den symbolischen Buchstaben, nicht den Variablennamen selbst!):

user@sonne> echo $-
himBH

Aktiv sind im Beispiel: hashall, interactive, monitor, braceexpand, histexpand.

$_

In Abhängigkeit vom letzten Vorgang steht entweder das letzte Argument des zuletzt ausgeführten Shellskripts (oder der zuletzt gerufenen Shell) drin oder, wenn die Mailboxen auf neue Nachrichten überprüft werden, der Name der aktuell betrachteten Datei.

user@sonne> bash -c echo a b c
user@sonne> echo $_
c

Die Prompts

Arbeitet man interaktiv mit der Bash, zeigt die Shell ihre Bereitschaft zur Entgegennahme von Eingaben anhand eines Prompts ("Eingabeaufforderung") an. Vier Prompts werden unterschieden, wobei zwei die Eingabeaufforderung symbolisieren:

  1. Primäres Prompt: Dieses zeigt an, dass die Bash eine neue Eingabe erwartet.
  2. Sekundäres Prompt: Wurde die Eingabe eines Kommandos nicht vollendet und dennoch versucht, die Zeile durch "ENTER" abzuschließen, zeigt die Bash dieses "Fortsetzungsprompt". Typische Situationen, die das Prompt hervorzaubern, sind: der Abschluss einer Zeile mit dem Backslash \, eine vergessene schließende Klammer oder Anführungszeichen:

    user@sonne> (echo "Das Zeilenende kam eindeutig [ENTER]
    > zu zei\[Enter]
    > tig"[Enter]
    > )[Enter]
    das Zeilenende kam eindeutig
    zu zeitig
    user@sonne>
  3. Drittes Prompt: (nur bash) Dieses Prompt erscheint bei der interaktiven Arbeit mit dem builtin-Kommando select und ist über die Shellvariable "$PS3" konfigurierbar.
  4. Viertes Prompt: (nur bash) Im Debug-Modus (set -x) wird dieses Prompt ("$PS4") vor jeder expandierten Kommandozeile ausgegeben. Der Modus ist hilfreich, um zu sehen, wie die Bash eine Eingabe expandiert, bevor sie diese an das Kommando zur Ausführung übergibt:

    user@sonne> testvar=text
    user@sonne> echo $testvar
    text
    user@sonne> set -x
    user@sonne> echo $testvar
    + echo text
    text


Das Aussehen der beiden ersten Prompts wird durch den Inhalt der beiden Variablen "$PS1" (primäres Prompt) und "$PS2" (sekundäres Prompt) bestimmt und kann in begrenztem Umfang geändert werden. Zunächst schauen wir uns den üblichen Inhalt der Variablen an:

user@sonne> echo $PS1
\u@\h:>
user@sonne> echo $PS2
>

"$PS1" enthält offensichtlich Sonderzeichen, die zum Nutzerkennzeichen \u bzw. zum Rechnernamen \h expandieren. Wichtige mögliche Zeichen sind:

\e

Leitet eine Escape-Sequenz ein

\d

Datum

\h

Rechnername bis zum ersten enthaltenen Punkt

\s

Shellname

\t

Aktuelle Zeit

\u

Nutzername

\w

Aktueller Pfadname

\$

"#", falls UID=0 und "$" sonst

\nnn

Die Zahl nnn wird als ASCII-Code interpretiert. So lassen sich auch nichtdruckbare Zeichen und Escape-Sequenzen ("\033" entspricht "\e")verwenden.

Das folgende Beispiel ändert den primären Prompt, so dass Nutzername, aktueller Pfad und das Datum angezeigt werden:

user@sonne> PS1="\u:\w:\d>"
user:~:Wed Jun 7>

Vielleicht wäre etwas Farbe für das Prompt noch besser? Mit Escape-Sequenzen lässt sich auch dieses realisieren:

user@sonne> PS1="\[\033[01;31m\]$PS1\[\033[m\]"
user@sonne>

Die Erklärung und weitere Informationen zum Umgang mit Farben für die Konsole findet der interessierte Leser im Anhang Skriptsammlung.

Konfiguration der Bash mit set und shopts

set und shopts sind zwei wichtige eingebaute Kommandos der Bash, mit Hilfe derer sich das Verhalten der Shell auf vielfache Art und Weise konfigurieren lässt. Eigentlich eher in den Initialisierungsdateien angewendet, lassen sich beide Kommandos auch interaktiv verwenden.

Beginnen wir mit set und betrachten zunächst den Status der von einer typischen Bash gesetzten Variablen:

user@sonne> set -o
allexport       off
braceexpand     on
errexit         off
hashall         on
histexpand      on
keyword         off
monitor         on
noclobber       off
noexec          off
noglob          off
notify          off
nounset         off
onecmd          off
physical        off
privileged      on
verbose         off
xtrace          off
history         on
ignoreeof       on
interactive-comments    on
posix           off
emacs           on
vi              off

Für jede der in obigem Beispiel aufgeführten Variablen kennt set eine "kurze" Option, um diese zu aktivieren (on) bzw. zu deaktivieren. Aus der Fülle der Optionen genügt die Kenntnis der schon erwähnten Option -o. Ohne Angabe weiterer Argumente werden alle Variablen (die mit set manipuliert werden können) angezeigt. Folgt dieser der (die) Namen einer (mehrerer) Variablen, so wird (werden) diese aktiviert. Zum Deaktivieren dient die Option +o in Verbindung mit einem Variablennamen.

# Aktivieren einer Variable mit set -o ...
user@sonne> set -o allexport noclobber
user@sonne> set -o | egrep 'allexport|noclobber'
allexport       on
noglob          on

# Deaktivieren einer Variable mit set +o ...
user@sonne> set +o allexport
user@sonne> set -o | egrep 'allexport|noclobber'
allexport       off
noglob          off

Viele Variablen deuten ihren Zweck bereits durch ihren Namen an. Die nützlichsten Vertreter möchten wir Ihnen nicht vorenthalten:

allexport

Alle Variablen der Shell werden exportiert sobald sie erstellt oder verändert werden.

braceexpand

Schaltet die Klammernexpansion an bzw. ab.

hashall

Ist diese Option gesetzt, werden Kommandos, die die Bash bereits einmal gesucht hat (PATH), mit vollständigem Pfad in einer Hashtabelle gespeichert. Dadurch wird ein folgender Aufruf desselben Kommandos erheblich beschleunigt.

histexpand

Die Möglichkeit des Zugriffs auf zuvor eingegebene Kommandozeilen mittels eines vorangestellten ! wird ein- bzw. ausgeschalten.

monitor

Durch Deaktivieren wird die Statusmeldung eines im Hintergrund gestarteten Prozesses (»Job«) unterdrückt. In ähnlichem Zusammenhang steht notify, womit der Status eines beendeten Hintergrundprozesses ohne Verzögerung auf dem Bildschirm landet.

noclobber

Wird diese Variable aktiviert, lassen sich existierende Dateien nicht mittels Ein- und Ausgabeumleitung überschreiben:

user@sonne> touch testdatei; ls > testdatei
# Alles im grünen Bereich...
user@sonne> set +o noclobber'
user@sonne> ls > testdatei
bash: testdatei: cannot overwrite existing file

Wird die Ausgabeumleitung mittels ">|" realisiert, schützt auch "noclobber" nicht vorm Überschreiben.

noexec

Kommandos werden zwar gelesen, aber nicht ausgeführt. Diese Variable wird gern in der Testphase während der Erstellung von Shellskripten gesetzt.

noglob

Die Pfadnamensexpansion kann ein- und ausgeschalten werden.

onecmd

Die Shell wird nach Ausführung des ersten Kommandos beendet. Eine denkbare Anwendung wäre in Verbindung mit Postfächern, wo ein Benutzer sich "von außen" anmeldet, durch ein Skript ein Kommando, das die Mail überträgt, gestartet wird und anschließend der Benutzer automatisch ausgeloggt wird. Damit erhält ein Benutzer niemals vollwertigen Zugang zum Rechner.

posix

Die Bash verhält sich nach IEEE POSIX P1003.2/ISO 9945.2 Shell und Tools Standard.

emacs, vi

Schaltet das Verhalten des Kommandozeileneditors auf den jeweiligen Modus.

shopt ist ab der Bash-Version 2.0 neu hinzugekommen. Es behandelt weitere Shellvariablen und kennt (leider) abweichende Optionen. So verhilft "-p" zu einem Blick auf den Status der von "shopt" verwalteten Variablen:

user@sonne> shopt -p
shopt -u cdable_vars
shopt -u cdspell
shopt -u checkhash
shopt -s checkwinsize
shopt -s cmdhist
shopt -u dotglob
shopt -u execfail
shopt -s expand_aliases
shopt -u extglob
shopt -u histreedit
shopt -u histappend
shopt -u histverify
shopt -s hostcomplete
shopt -u huponexit
shopt -s interactive_comments
shopt -u lithist
shopt -u mailwarn
shopt -u no_empty_cmd_completion
shopt -u nocaseglob
shopt -u nullglob
shopt -s progcomp
shopt -s promptvars
shopt -u restricted_shell
shopt -u shift_verbose
shopt -s sourcepath
shopt -u xpg_echo

Um eine Shellvariable zu aktivieren, ist die Option -s zu verwenden. Dem entgegen schaltet -u die Wirkung der Variablen ab. Wiederum betrachten wir nur eine Auswahl der Variablen im Detail:

cdspell

Ist die Option gesetzt, vermag die Shell geringe Tippfehler in Pfadangaben zu korrigieren:

user@sonne> cd /us/x11R6
bash: cd: /us/x11r6/: Datei oder Verzeichnis nicht gefunden
user@sonne> shopt -s cdspell
user@sonne> cd /us/x11R6
/usr/X11R6/

lithist

Bei der Eingabe von Kommandos, die über mehrere Zeilen gehen, speichert die Bash diese normalerweise ohne die enthaltenen Zeilenumbrüche in der History. Wenn Sie diese Variable auf "on" setzen, bleiben diese Zeilenumbrüche auch in der History enthalten.

dotglob

Bei der Pfadnamensexpansion werden Dateien, deren Namen mit einem Punkt beginnen, nur berücksichtigt, wenn diese Variable gesetzt ist:

# Im Beispiel existiert im aktuellen Verzeichnis einzig eine Datei ".foo"#
user@sonne> ls *
ls: *: Datei oder Verzeichnis nicht gefunden

user@sonne> shopt -s dotglob
user@sonne> ls *
.foo

mailwarn

Ist diese Variable gesetzt, überprüft die Bash die Mailboxen auf neue Nachrichten und gibt ggf. eine Mitteilung aus. Die Option korrespondiert mit den Variablen MAIL und MAILPATH.

interactive_comments

Ist die Option gesetzt, gilt alles, was auf der Kommandozeile einem Doppelkreuz # folgt, als Kommentar

nocaseglob

Ist diese Option gesetzt, spielt Groß- und Kleinschreibung von Dateinamen für die Bash keine Rolle:

user@sonne> ls .F*
ls: .F*: Datei oder Verzeichnis nicht gefunden

user@sonne> shopt -s nocaseglob
user@sonne> ls -F*
.foo

restricted_shell

Diese Option ist gesetzt, wenn die Bash im restricted Modus gestartet wurde; sie kann nicht geändert werden

Die weiteren Variablen sind wohl selten von Nutzen, es sei denn, jemand wünscht gezielt bestimmte Expansionsmechanismen zu deaktivieren.

Aliasse

Zur Abkürzung immer wiederkehrender Kommandofolgen lassen sich für diese so genannte Aliasse definieren. Ein Alias wird mit dem Kommando alias erzeugt:

user@sonne> alias dir='ls -l'
user@sonne> alias md=mkdir
user@sonne> alias rd=rmdir
user@sonne> alias rename=mv
user@sonne> alias cdlin='cd /usr/src/linux'

Ein Alias wird genauso benutzt wie das entsprechende Kommando:

user@sonne> dir /
insgesamt 65
drwxr-xr-x   2  root     root         2048 Dec 14 13:23 bin
drwxr-xr-x   3  root     root         1024 Dec 21 10:59 boot
drwxr-xr-x   2  root     root         1024 Dec 14 13:05 cdrom
drwxr-xr-x   6  root     root        30720 Dec 29 08:50 dev
...
user@sonne> md directory
user@sonne> rd directory
user@sonne> rename nt refuse
user@sonne> cdlin; pwd
/usr/src/linux

Ein Alias existiert bis zum Ende der Shell, in der er definiert wurde oder bis zum expliziten Löschen mittels unalias:

user@sonne> unalias dir
user@sonne> unalias cdlin
user@sonne> cdlin
bash: cdlin: command not found

# ein Alias ist nur in der Shell seiner Definition bekannt:
user@sonne> bash
user@sonne> md tmp
bash: md: command not found

Ein Aufruf von alias ohne Argumente bewirkt eine Auflistung aller definierten Abkürzungen.

Funktionen

Eine Funktion ist ein Name für ein Kommando oder für eine Gruppe von Kommandos. Funktionen werden vorrangig in Shellskripten verwendet, um wiederkehrende Kommandosequenzen nicht ständig neu schreiben zu müssen. Ein großer Vorteil von Funktionen ist, dass sich diese in einer Datei speichern lassen und diese Datei von anderen Skripten geladen werden kann.

Eine Funktion wird wie folgt definiert:

Format: [function] Funktionsname() { Kommando; [Kommando;] }

Bei der Verwendung von Funktionen sind einige Regeln zu befolgen:

  1. Deckt sich der Name der Funktion mit einem builtin-Kommando, wird immer die Funktion ausgeführt und niemals das Kommando. Ebenso verdeckt ein Funktionsname ein gleichnamiges Kommando:

    user@sonne> type test
    test is a shell builtin
    user@sonne> test(){ echo "ich bin eine Funktion"; }
    user@sonne> type test
    test is a function
    test ()
    {
    echo "ich bin eine Funktion"
    }
    user@sonne> unset test


  2. Die Funktion muss vor ihrer Verwendung definiert sein.
  3. Eine Funktion läuft in der aktuellen Umgebung, d.h. alle Variablen der Umgebung sind sichtbar und alle Variablen, die in der Funktion definiert wurden, sind auch außerhalb sichtbar:

    user@sonne> func(){ var_in_func=xxx; }
    user@sonne> func
    user@sonne> echo $var_in_func
    xxx


  4. Wird eine Funktion mittels »exit« verlassen, wird auch der rufende Prozess beendet:

    user@sonne> func(){ exit; }
    user@sonne> func
    login:


  5. Der Rückgabewert einer Funktion ist der Rückgabewert des letzten in ihr gestarteten Kommandos:

    user@sonne> func(){ grep -q foo /etc/passwd; echo $?; }
    user@sonne> func
    1
    user@sonne> echo $?
    0
    user@sonne> func(){ return 255; }
    user@sonne> func
    user@sonne> echo $?
    255


  6. Funktionen sind nur in der Shell ihrer Definition bekannt:

    user@sonne> func(){ echo "lokal"; }
    user@sonne> bash
    user@sonne> func
    bash: func: command not found
    user@sonne> exit
    user@sonne> func
    lokal




Einer Funktion können Parameter als Argumente übergeben werden:

Aufruf: Funktionsname [Arg1] [Arg2] ...

Innerhalb einer Funktion kann auf die Parameter mittels der Positionsparameter $1..$9 zugegriffen werden. Als Beispiel dient eine Funktion "square", die die als Argument übergebene Zahl quadriert:

user@sonne> square() { test -z $1 && return 1; expr $1 \* $1; }
user@sonne> square
user@sonne> square 18
324

Erklärung: Das builtin-Kommando test liefert den Status "0", falls die Variable "$1" leer ist (kein Argument). In diesem Fall wird "return 1" ausgeführt und der Funktionsaufruf beendet. expr berechnet den Ausdruck und schreibt das Ergebnis auf die Standardausgabe.

Eingebaute Kommandos

Es liegt wohl in der Natur des Linux-Neulings, seine Testprogramme und -skripten «"test« zu benennen (eigentlich kann ich mich an keinen Linuxkurs erinnern, indem nicht mindestens einer der Teilnehmer auf diese Weise mit den builtin Kommandos der Bash konfrontiert wurde). Nach verrichteter Arbeit zeigte der Testlauf:

user@sonne> ls -l test
-rwxr-xr-x  1   user    users       12177 Sep 23 10:52 test
user@sonne> test

...nichts...? Mit Kenntnis des Vorgehens der Bash bei der Suche nach einem Kommando, gelangt man bald zum Schluss, dass sich ein builtin Kommando vorgedrängelt hat.

Es gibt eine Fülle solcher eingebauter Kommandos und mindestens 4 Gründe, warum solche in der Bash überhaupt existieren:

  1. Weil es ein solches Kommando in Unix nicht gibt (Beispiel "source")
  2. Weil ein builtin Kommando effizienter arbeitet, als ein externes Kommando (keine Prozesserzeugung notwendig; Beispiel »echo«)
  3. Weil nur ein eingebautes Kommando Bash-interne Variablen ändern kann (Beispiel »export«)
  4. Weil ein Kommando wie »exec« nur innerhalb der Bash realisierbar ist

Betrachten wir die einzelnen builtin-Kommandos:

:

Dieses »Kommando« tut nichts, außer einen Rückgabewert »0« zu erzeugen (»0« ist der übliche Rückgabewert eines Kommandos unter Unix, wenn seine Ausführung erfolgreich war). Nützlich ist es in Shellskripten, falls Sie in Bedingungen einen wahren Wert (»true«) benötigen oder an Positionen, wo syntaktisch ein Kommando erwartet wird, Sie aber keines benötigen:

user@sonne> while : ;do echo "Eine Endlosschleife"; done
Eine Endlosschleife
Eine Endlosschleife
Eine Endlosschleife
...
Eine Endlosschleife[Ctrl]+[C]
user@sonne> if test -a foo ; then :; else echo "Datei nicht existent"; fi
Datei nicht existent

. <Datei> bzw. source <Datei>

Die angegebene Datei wird gelesen und innerhalb der Shellumgebung ausgeführt. Ist die Datei ohne Pfad angegeben, wird dabei in Verzeichnissen der PATH-Variable gesucht. Wird sie dort nicht gefunden, wird das aktuelle Verzeichnis betrachtet. Das Kommando kann sich der C-Programmierer als "include" der Bash vorstellen.

Manche Linuxdistributionen (bspw. RedHat) verwenden dieses Kommando in ihren Runlevel-Skripten, um eine Funktionsbibliothek einzubinden.

Auf der Kommandozeile bietet sich "source" an, um die initiale Umgebung zu rekonstruieren (weil man u.U. die Umgebungsvariablen "verbogen" hat):

user@sonne> source /etc/profile
# bzw.
user@sonne> . /etc/profile

alias

Dient der Definition einer Abkürzung für ein(e) Kommando(folge). Mit der Option -p werden alle vorhandenen Aliasse aufgelistet; Beispiele wurden bereits im einleitenden Abschnitt zu Bash (Eingabehilfen) und in Allgemeines zu Shells genannt.

bg

Listet die Jobnummern aller Hintergrundprozesse auf.

bind

Das Kommando besitzt in erster Linie im Zusammenspiel mit der Datei /etc/inputrc (bzw. ~/.inputrc) eine Rolle. Besagte Dateien enthalten die »Keybindings«, also die Zuordnungen von Tastenkombinationen zu bestimmten Funktionalitäten. Wenn bspw. die unter Interaktive Bash aufgeführten Eingabehilfen bei Ihnen nicht funktionieren sollten, dann fehlt in der Konfiguration Ihrer Bash die notwendige Zuordnung (oder Sie verwenden eine ältere Bashversion).

bind kann auch interaktiv benutzt werden, um bestimmte Keybindings zu löschen, um sie sich anzeigen zu lassen oder neue zu definieren. Letzteres ermöglicht gar die Ausführung von Kommandos bei Betätigung einer zuvor definierten Tastensequenz. Benötigen Sie solche Sequenzen häufig, so nehmen Sie sie in ihre persönliche ~/.inputrc auf.

Wenn Sie mit der Bash einigermaßen per Du sind, so kennen Sie die eine oder andere Tastenkombination, um geschwind auf der Kommandozeile zu navigieren oder diese zu manipulieren. Hinter den Mechanismen verbergen sich readline-Funktionen. Welche es gibt, verrät der Aufruf »bind -l« und über deren aktuelle Belegung weiß »bind -p« bescheid. Erscheint in letzter Ausgabe »not bound«, so ist diese Funktion nicht belegt.

user@sonne> bind -p | head -5

"\C-g": abort
"\C-x\C-g": abort
"\e\C-g": abort
"\C-j": accept-line

Angenommen Sie verfassen im vi ein deutschsprachiges html-Dokument. Um die Zeichen »ä«, »ö«,... in Browsern, die deutsche Zeichensätze nicht unterstützen, korrekt darzustellen, sollten alle länderspezifischen Zeichen im Unicode dargestellt werden, also bspw. »&uuml;« anstatt »ü«. Die komfortable Lösung ist das Verändern der Tastaturbelegung, so dass automatisch der Unicode im Text erscheint. In der Bash erreichen Sie dies wie folgt:

user@sonne> bind '"ü":"&uuml;"'

Sobald Sie »ü« tippen, erscheint in der Eingabe »&uuml;«. Um die Bindung aufzuheben, geben sie »bind -r <Tastensequenz>« an; aber für gewöhnlich besteht der Wunsch, die alte Belegung wieder herzustellen. Eine Taste, die »als sie selbst« definiert wird, wird in folgender Manier belegt:

user@sonne> bind 'ü:self-insert'

Obige Form der Tastenbindung nennt man auch Makro; daneben können sie auch Funktionen und gar Kommando(s) an Tasten(sequenzen) binden. Das nachfolgende Beispiel bindet [Ctrl]+[b] an die Ausgabe des Kalenders des aktuellen Monats:

user@sonne> bind -x '"\C-b":cal'
user@sonne> [Ctrl]+[B]
    November 2000
So Mo Di Mi Do Fr Sa
          1  2  3  4
 5  6  7  8  9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30

Ihre gesammelten Werke der Tastenbindungen können Sie auch in eine beliebige Datei schreiben und diese mittels "bind -f <Datei>" laden.

break [n]

Dient zum expliziten Verlassen einer Schleife. Ohne Angabe eines Arguments wird die unmittelbar umgebende Schleife verlassen; möchte man tiefere Verschachtelungen verlassen, muss die Tiefe angegeben werden:

...
while [ Bedingung ]; do
   for i in Liste; do
      case "$i" in
        foo* ) break;
        bla* ) tue etwas ;;
        * ) Fehler; break 2;
      esac
   done
done
...

builtin Kommando

Bei der Suche nach Kommandos betrachtet die Shell Aliasse und Funktionen noch vor den eingebauten Kommandos. Überdeckt nun ein solcher Name ein builtin-Kommando, so wird beim einfachen Aufruf immer der Alias bzw. die Funktion ausgeführt werden. Mit vorangestelltem builtin weist man nun die Bash an, auf jeden Fall ihr eingebautes Kommando aufzurufen. Der Rückgabestatus ist gleich dem Rückgabewert des Builtin's oder "falsch", falls das Kommando kein builtin ist.

cd

Zum Wechsel des aktuellen Verzeichnisses; Details stehen unter NutzerkommandosDateien und Verzeichnisse.

command Kommando

Ein weiterer Weg, um die Verwendung von Aliassen oder Funktionen bei der Suche nach einem Kommando temporär auszuschalten, ist ein dem zu startenden Kommandonamen voranzustellendes command. Die Shell sucht nun einzig in der Liste der eingebauten Kommandos und den Pfaden aus PATH. Hilfreich ist die Option -p, falls die PATH-Variable einmal "völlig daneben" belegt ist; die Bash sucht in default-Pfaden und findet so zumindest die wichtigsten Programme.

compgen

Mit dem Kommando lassen sich die möglichen Expansionen anzeigen. Um bspw. gezielt die denkbaren Erweiterungen aller mit "l" beginnenden Aliasse zu erhalten, ist folgende Kommandozeile notwendig:

user@sonne> compgen -A alias l
l
la
ll
ls
ls-l

Anstatt von "alias" können u.a. Funktionen ("function"), Schlüsselworte der Bash ("keywords"), Dateinamen ("file"), Verzeichnisse ("directory"), Variablen ("variable") expandiert werden.

Noch weit reichender sind die Möglichkeiten in Bezug auf Dateinamen, da hier mit Suchmustern gearbeitet werden kann:

user@sonne> compgen -G '*c*'
Packages
bla.xcf
countdir
linuxbuch
Documents

Abgesehen von -r und -r verwendet compgen dieselben Optionen wie das nachfolgend beschriebene complete.

complete

Mit diesem Kommando kann das ganze Verhalten der Bash bei der Vervollständigung von Argumenten verändert werden. Bevor wir uns mit den Optionen auseinander setzen, soll ein Beispiel die Mächtigkeit des Konzepts andeuten.

Beispiel: Als Argumente für das Programm xv (dient der Anzeige von Dateien diverser Grafikformate) sollen bei der automatischen Dateinamensergänzung nur Dateien mit einer typischen Dateiendung (*.jpg, *.gif, ...) berücksichtigt werden. Mit complete müsste die Bash wie folgt eingerichtet werden:

# Die Option "extglob" (vergleiche shopt) muss gesetzt sein:
user@sonne> shopt -q extglob || shopt -s extglob

# Test des bisherigen Verhaltens:
user@sonne> xv s[Tab][Tab]
sax_0.gif     sax_norm.tmp  sax_xxx.jpg
sax_grob.tmp  sax_post.tmp  start_taper

user@sonne> complete -f -X '!*.+(gif|jpg|jpeg|GIF|JPG|bmp)' xv
# Test des jetzigen Verhaltens:
user@sonne> xv s[Tab][Tab]
sax_0.gif  sax_xxx.jpg

Mit der Option -p lässt sich jede Spezifikation anzeigen und mit -r auch wieder löschen. Wird kein Name angegeben, betrifft die Aktion jede Spezifikation:

user@sonne> complete -p
complete -f -X '!*.+(gif|jpg|jpeg|GIF|JPG|bmp)' xv
user@sonne> complete -r xv

complete wurde im Beispiel durch die Option -f angewiesen, den folgenden Ausdruck einer Dateinamenserweiterung zu unterziehen. Alternative Expansionen sind (Auswahl):

-a

Es wird eine Expansion zu einem bekannten Aliasnamen versucht

-b

Erweiterung zu einem eingebauten Kommando

-c

Erweiterung zu einem Kommando

-e

Exportierte Variable

Als Beispiel definieren wir eine Funktion, die den Inhalt einer exportierten Variablen anzeigen soll:

user@sonne> show_exports() { echo $*; }

Beim Aufruf dieser Funktion sollen bei versuchter Expansion alle möglichen Erweiterungen angezeigt werden. Um sich den Inhalt der Variablen selbst anzuschauen, muss dieser ein Dollarzeichen voranstehen. Mit der Option -P lässt sich ein solches Präfix für ein Expansionsergebnis fest legen. Die complete-Zeile sollte wie folgt aussehen:

user@sonne> complete -e -P '$'
# Test
user@sonne> show_exports PO[Tab]
user@sonne> show_exports $POVRAYOPT
-l/usr/lib/povray/include

-f

Dateinamenserweiterung

-u

Benutzernamen

Als Beispiel soll das Kommando mail mit einem Benutzernamen expandiert werden. Als Suffix wird automatisch "@outside.all" ergänzt:

user@sonne> complete -u -S '@outside.all' mail
# Test
user@sonne> mail a[Tab]
user@sonne> mail alf@outside.all

-v

Variablennamen

Als weitere Optionen stehen zur Verfügung:

-G Muster

Es wird eine Dateinamenserweiterung mit dem Muster versucht

-C Kommando

Das Kommando wird in einer Subshell gestartet und dessen Ausgabe als mögliche Vervollständigung eingesetzt

-X Muster

Es wird eine Dateinamenserweiterung nach Ausführung aller anderen Optionen versucht. Alle Dateinamen, die mit dem Muster übereinstimmen, werden aus der Liste der möglichen Vervollständigungen entfernt

-P Präfix

Nach Ausführung aller anderen Optionen werden die möglichen Erweiterungen mit dem angegebenen Präfix versehen

-S Suffix

Nach Ausführung aller anderen Optionen werden die möglichen Erweiterungen mit dem angegebenen Suffix versehen

continue [n]

Dieses Kommando kann innerhalb von Schleifen verwendet werden, um unmittelbar mit dem nächsten Schleifendurchlauf fortzufahren. Ohne Angabe von "n" ist die umgebende Schleife gemeint; mit Angabe einer ganzen Zahl "n" kann im Falle von verschachtelten Schleifen diejenige benannt werden, die zu durchlaufen ist

for i in $(ls); do
   test -d $i && continue
   # tue etwas...
done

declare bzw. typeset

Zum Deklarieren von Variablen, wobei diese gleichzeitig mit Attributen versehen werden können; siehe unter Variablen

dirs

Dient zur Anzeige des Verzeichnisstacks. Ein Verzeichnis kann neben dem bekannten Kommando cd auch mit dem eingebauten Kommandos pushd bzw. popd gewechselt werden. Bei letzterem Vorgehen wird das Verzeichnis auf einem Stack abgelegt, dessen Einträge popd in umgekehrter Reihenfolge entfernt und in das jeweilige Ausgangsverzeichnis wechselt.

disown [Jobnummer(n)]

Das Kommando ist die bash-eigene Realisierung des Kommandos nohup und ermöglicht, Jobs nachträglich vom Elternprozess (also die Shell) zu lösen. D.h. bei Beendigung der Shell erhalten diese Prozesse kein Signal SIGHUP, sodass sie weiterhin existieren. Gleichzeitig werden die Jobs aus der Jobtabelle entfernt, sodass sie bspw. vom Kommando jobs nicht mehr berücksichtigt werden können. Die Option -h schützt einen Prozess vor SIGHUP ohne ihn aus der Jobtabelle zu verbannen. Mit -a kann disown auf alle Jobs und mit -r auf alle laufenden (Status "Running") Jobs ausgedehnt werden.

echo

Gibt alle Argumente, getrennt durch ein Leerzeichen, und einen abschließenden Zeilenumbruch aus. Die Option -n unterdrückt den Zeilenumbruch und -e erzwingt die Auswertung Escape-Sequenzen:

user@sonne> echo "\a"
\a
user@sonne> echo -n "\a"
\auser@sonne> echo -e "\a"
PIEP

Das nächste Beispiel verwendet "\b", um den Cursor um eine Position zurück zu bewegen:

user@sonne> cat ticker
#!/bin/sh

declare -i zeit=0
echo -en "Sekunden: \t"

while :; do
   for ((i=${#zeit}; i; i--)); do
      echo -en "\b"
   done
   echo -en $zeit
   zeit=zeit+1
   sleep 1
done

enable

Die eingebauten Kommandos der Shell lassen sich bei Bedarf aktivieren oder auch abschalten. Letzteres kann nützlich sein, wenn Sie fortwährend auf ein externes Kommando zugreifen müssen, das denselben Namen wie ein Shell-Builtin besitzt. Da die Shell immer ihr eigenes Kommando bevorzugt, müssten Sie ständig den vollständigen Pfad eintippen - auf die Dauer ein lästiges Unterfangen. Mit -n <Kommando> deaktivieren Sie das Builtin; ohne Eingabe einer Option lässt es sich wieder aktivieren.

eval

Die Expansionen der Bash laufen nach wohldefinierten Regeln ab. Wurden sie der Reihe nach behandelt, wird die resultierende Kommandozeile ausgeführt. Manchmal enthält diese Kommandozeile wiederum Bestandteile, die man einer Expansion gern unterziehen würde. Genau hierzu verhilft eval:

user@sonne> var=foo
user@sonne> bla='$var'
user@sonne> echo $bla
$var
user@sonne> eval echo $bla
foo

exec

Das Kommando besitzt zwei Bedeutungen. Wird ihm als Argument ein Kommandoname mitgegeben, so wird das aktuell ausgeführte Programm (also die Shell bzw. ein Shellskript) durch dieses neue Programm ersetzt. Das hat zur Folge, dass mit Beendigung des Kommandos auch die Shell nicht mehr existiert. Diese Art der Anwendung von exec hat seinen Ursprung vermutlich in Zeiten begrenzter Hardwareressourcen. So "optimierte" man Shellskripte dahingehend, dass der letzte Befehl per exec gestartet wurde. Dies ersparte eine weitere Prozesserzeugung und verlief somit etwas schneller, als es mit einem neuen Prozess der Fall gewesen wäre (und es sparte den (Speicher)Overhead für den Prozess). Nützlich mag das Kommando in dem Sinne höchstens zum Ersetzen der aktuellen Shell durch eine andere sein:

user@sonne> echo $SHELL
/bin/bash
user@sonne> exec tcsh
/home/user>

Der zweite - und wichtigere - Anwendungsbereich für exec ist die Zuordnung eines Dateideskriptors zu einer Datei.

Angenommen, Sie möchten alle Fehlerausgaben eines Shellskripts in eine Datei umleiten. Mit den bislang bekannten Mitteln könnten sie entweder alle kritschen Kommandos im Shellskript separat umleiten oder Sie könnten alle Kommandos gruppieren und die Fehlerausgabe gemeinsam abfangen. Einfacher ist es, folgende Zeile an den Anfang des Skripts zu setzen:

exec 2>error.log

Erläuterung: Hiermit wird die Fehlerausgabe (Deskriptor 2) mit der Datei "error.log" verbunden. Mit Beendigung des Shellskripts geht die Bindung wieder verloren.

Das zweite Beispiel demonstriert das Öffnen einer Datei mit exec, sodass die Eingabe aus dieser bezogen wird. Wir simulieren das Kommando nl, das die Zeilen seiner Eingabe nummeriert:

user@sonne> (typeset -i n=1; exec < testdat;
> while read line; do
> echo "$n $line"; n=n+1;
> done)
1 eins
2 zwei
3 drei
4 vier

Erläuterung: Die Testdatei enthält die Zeilen "eins", "zwei" usw.; die Eingabe wird aus dieser bezogen. Mittels read wird zeilenweise gelesen und jede Zeile mit vorangestellter Nummer ausgegeben. Das Rechnen mit "n" ist möglich, da die Variable zuvor als Ganzzahl vereinbart wurde. Alles wurde in einer Subshell gestartet, damit die aktuelle Shell nicht beendet wird.

Ein letztes Beispiel soll die Thematik vorerst beenden (die Shellprogrammierung ist ein wesentlich ergiebigeres Anwendungsfeld für exec). Es demonstriert eine effiziente Möglichkeit, eine konkrete Zeile einer Datei zu manipulieren. Unsere Testdaten seien die Folgenden:

user@sonne> cat testfile
Erste Zeile.
Zweite Zeile.
Zweite Zeile.
Vierte Zeile.

Aus der Datei müssen wir nicht nur lesen, sondern gleichzeitig in diese schreiben können. Wir werden nach der Zeile "Zweite Zeile." suchen und die folgende Zeile durch "Dritte Zeile." ersetzen. Eine Kommandofolge, die dies realisiert, ist diese:

user@sonne> (exec <>testfile 1>&0
> while read line; do
> echo $line | grep -q "Zweite*" && echo -n "Dritte Zeile."
> done)

Erläuterung: Die Eingabedatei zum Lesen und Schreiben zu öffnen, würde allein nichts nützen, da hiermit nur der Dateideskriptor (Nummer 0) betroffen ist. Deshalb muss die Standardausgabe ebenfalls auf diesen Deskriptor umgelenkt werden. Wird die mit "Zweite..." beginnende Zeile gefunden, zeigt der Dateizeiger bereits auf den Beginn der dritten Zeile. Deshalb landet die Ausgabe "echo..." genau dort.

Wenn Sie Zweifel an den Optionen von grep bzw. echo hegen, so testen Sie, was passiert, wenn Sie diese entfernen.

exit [n]

Beendet die Shell (das Shellskript) mit dem angegebenem Rückgabewert. Wird kein Wert angegeben, liefert exit den Status des letzten Kommandos.

export

Dient zum exportieren von Variablen und - mit der Option -f von Funktionen, so dass diese ab der (Shell)Prozess ihrer Definition auch in allen abgeleiteten Prozessen sichtbar sind ("globale Variablen"). Mit der Option -n kann die export-Eigenschaft entzogen werden. -p listet alle exportierten Variablen und Symbole auf:

user@sonne> PATH=$PATH:/usr/local/bin
user@sonne> export PATH

fc

fg [Job]

Holt den angegebenen ("Jobnummer") bzw. den zuletzt gestarteten Hintergrundprozess in den Vordergrund:

user@sonne> sleep 20& sleep 10&
[1] 1233
[2] 1234
user@sonne> fg
sleep 10

[1]   Done                  sleep 20
user@sonne> sleep 20& sleep 10&
[1] 1236
[2] 1237
user@sonne> fg 1
sleep 20

[2]   Done                  sleep 10

getopts OPTIONEN Variable

Jeder Programmierer kennt den Aufwand beim Parsen der an ein Programm übergebenen Argumente und Optionen. Sind die Optionen erlaubt? Stimmt die Reihenfolge? Ist das Argument zulässig?...?

Die meisten Programmiersprachen bringen in ihrem Sprachschatz eine Funktion mit, die eine solche Auswertung wesentlich vereinfacht. Die Funktion der Bash ist »getopts«. Dieses Kommando erlaubt einem Shellskript die unter Unix übliche Gruppierung von Optionen zu verwenden. Anstatt "-l -a -z" kann auch "-laz" oder "-a -lz"... geschrieben werden. Solche Angaben über die bereits besprochenen Positionsparameter auszuwerten, ist zwar denkbar, aber mehr als verzwickt.

Im Zusammenhang mit der Bashprogrammierung werden wir »getopts« extensiv verwenden, an dieser Stelle soll ein kleines Beispiel das Gefühl für die Anwendung schulen.

Ein Skript soll die Optionen "-a", "-l", "-F" und "-f <Datei>" verstehen. Beachten Sie, dass "-f" die Angabe eines Arguments erfordert. Die notwendige Zeichenkette der OPTIONEN ist "alf:F". Die Reihenfolge der Optionen ist unwichtig, entscheidend ist der Doppelpunkt hinter "-f:", der »getopts« mitteilt, dass dieser Option ein Argument folgen muss:

user@sonne> cat parseline
#!/bin/sh

while getopts alf:F Optionen; do
   case $Optionen in
      a) echo "Option a";;
      l) echo "Option l";;
      f) echo "Option f Argument ist $OPTARG";;
      F) echo "Option F";;
   esac
done

user@sonne> parseline -aF -f
Option a
Option F
./parseline: option requires an argument -- f

user@sonne> parseline -f so_gehts -l
Option f Argument ist so_gehts
Option l

Benötigt eine Option ein Argument, kann auf dieses über die Variable OPTARGS zugegriffen werden; OPTIND enthält zu jedem Zeitpunkt den Index auf die nächste von getopts zu betrachtende Option. In Shellskripten wird man Fehlermeldungen von getopts abfangen. Dies geht entweder über die Umleitung der Standardfehlerausgabe oder durch Setzen der Variable OPTERR auf 0.

hash

Ein auf der Kommandozeile angegebenes Kommando muss von der Shell gesucht werden. Die Suche, vor allem wenn sie die Angaben aus PATH einschließt, kostet Zeit. Deswegen merkt sich die Bash die Zugriffspfade zu allen externen Kommandos in einer Hashtabelle. Diese Hashtabelle wird zuerst konsultiert (es sei denn hashall ist nicht gesetzt; vergleiche set) und nur wenn der Pfad zu einem Kommando dort nicht erfasst wird, werden die Verzeichnispfade betrachtet. Beim nächsten Zugriff auf dasselbe Kommando wird man den Geschwindigkeitszuwachs deutlich spüren.

Zur Anzeige der Hashtabelle ist »hash« ohne Angabe von Argumenten aufzurufen:

user@sonne> hash
hits    command
   2    /bin/ls
   1    /bin/mv
   6    /bin/sh
   8    /usr/bin/vi
   1    /bin/chmod
   3    /bin/date
   1    /usr/bin/id
   1    /usr/bin/man

Ein Problem besteht nun mit gleichnamigen Kommandos. Liegt eines in der Hashtabelle vor, so kann auf das andere nur über die vollständige Pfadangabe zugegriffen werden. Soll dieses "benachteiligte" Kommando nun vermehrt eingesetzt werden, ist ein Löschen der Hashtabelle mittels »-r« sinnvoll:

user@sonne> hash -r
user@sonne> hash
hash: hash table empty

Indem »hash« mit einem oder mehreren Kommandonamen aufgerufen wird, werden diese Kommandos gesucht und in die Hashtabelle aufgenommen; sie werden jedoch nicht gestartet (sinnvoll ist dies eventuell in Startskripten). Des Weiteren kann mit »-p <Pfad_zum_Kommando>« ein solches mit vorgegebenem Pfad der Tabelle hinzufügen.

help

Schreibt einen kurzen Hilfetext zu einem eingebauten Kommando aus. Ein Beispiel hierzu steht in Erste Schritte - Hilfe.

history

Das Kommando dient zur Anzeige oder Manipulation des Kommandozeilenspeichers. Ohne Optionen gerufen, werden alle Einträge der Liste inklusive einer Zeilennummerierung aufgeführt; mit einer vorangestellten Zahl kann die Darstellung auf die letzten Einträge eingeschränkt werden:

user@sonne> history 5
  555 parseline -aF -f
  556 parseline -f bla -l huch
  557 mv parseline Scripts\&Programs/
  558 ll Linuxfibel/bash.htm
  559 history 5

Anhand der Nummerierung kann nun gezielt ein Eintrag entfernt werden (»-d <Nummer>«). »-c« löscht den gesamten Inhalt.

Der Kommandozeilenspeicher wird bei Beendigung der Shell in einer Datei gesichert, um diese explizit zu aktualisieren, kann »-a« bzw. »-w« genutzt werden, womit die neuen Einträge angehangen werden bzw. der alte Inhalt ersetzt wird.

Weitere Informationen zum Kommandozeilenspeicher findet man im gleichnamigen Abschnitt weiter unten.

jobs

Das Kommando zeigt die in der Jobtabelle erfassten Jobs (Hintergrundprozesse) an. Das Format der Ausgabe kann über Optionen gesteuert werden; im Zusammenhang mit Prozessen gehen wir weiter unten auf dieses Thema ein.

kill

Das Kommando dient der Steuerung bereits laufender Prozesse, indem es an diese Signale versendet. Während einige Signale vordefinierte Bedeutungen besitzen, können andere von den Prozessen nach eigenen Vorstellungen behandelt werden. Das Versenden von Signalen soll auch im Zusammenhang mit Prozessen diskutiert werden.

let

Für jedes Argument wird eine arithmetische Substitution versucht. Expandiert das letzte Argument zu 0, ist der Rückgabewert 1; sonst immer 0. »let« bietet somit eine Möglichkeit der Überprüfung, ob eine Variable eine Zahl >0 enthält:

user@sonne> failure=1234x
user@sonne> let $failure 2>/dev/null || echo "keine Zahl"
keine Zahl
user@sonne> let answer=6*7
42

local

Das Kommando kann nur innerhalb von Funktionen verwendet werden und dient der Definition lokaler Variablen. Somit ist sichergestellt, dass existierende Variablen gleichen Namens nichtversehentlich überschrieben werden:

user@sonne> var=foo
user@sonne> func() { var=bla; echo $var; }
user@sonne> func
bla
user@sonne> echo $var
bla
user@sonne> var=foo
user@sonne> func() { local var=bla; echo $var; }
user@sonne> func
bla
user@sonne> echo $var
foo

logout

Beendet eine Login-Bash und meldet den Benutzer ab. In einer Nicht-Login-Bash hagelt es eine Fehlermeldung.

popd

Entfernt den obersten Eintrag vom Verzeichnisstack und wechselt zum neuen obersten Verzeichniseintrag. Dieser Wechsel kann mit der Option »-n« unterdrückt werden. Um einen anderen Eintrag als den obersten zu entfernen, kann dieser mit »+Anzahl« angegeben werden. Der oberste Verzeichniseintrag selbst ist »+0«, der zweite »+1« usw. Mit »-Anzahl« beginnt die Zählung am unteren Ende des Stacks. Ein Beispiel zur Anwendung folgt beim korrespondierenden »pushd«.

printf "FORMAT" Argument[e]

Das Kommando verhilft zu einer formatierten Ausgabe analog zum printf der Programmiersprache C. Die FORMAT-Zeichenkette enthält hierfür Platzhalter mit optionalen Ausrichtungsparametern; die nachfolgenden Argumente müssen vom Typ her genau dem Typ des Platzhalters entsprechen.

Innerhalb der FORMAT-Zeichenkette können Escape-Sequenzen verwendet werden. Die wichtigsten sind »\n« (Zeilenumbruch), »\t« (Tabulator) und »\a« (akustisches Zeichen). Ein Platzhalter besitzt die Form »%[Ausrichtung]Symbol«, wobei die Ausrichtung eine Zahl ist, die die Anzahl darzustellender Zeichen des Arguments betrifft. Ist das Argument länger, werden die überschüssigen Zeichen abgeschnitten, ist es kürzer, werden Leerzeichen aufgefüllt. Mit einem optionalen Minus »-« vor der Zahl wird das Argument linksbündig angeordnet. Wichtige Symbole sind:

d

Eine ganze Zahl

s

Eine Zeichenkette

f

Rationale Zahl; hierbei kann die Anzahl darzustellender Vor- und Nachkommastellen angegeben werden: "%8.3f"

E

Darstellung rationaler Zahlen in Exponentenform.

Beispiele:

user@sonne> printf "Zeichenkette: %8s Zahl %d\n" test 42
Zeichenkette:     test Zahl 42
user@sonne> printf "Zeichenkette:\t%8s\tZahl %d\n" test 42
Zeichenkette:       test        Zahl 42
user@sonne> printf "Zeichenkette:\t%-8s\tZahl %3.1E\n" test 42
Zeichenkette:   test            Zahl 4,2E+01

pushd

Mit dem Kommando kann in ein angegebenes Verzeichnis gewechselt werden, wobei das Verzeichnis auf einem Stack abgelegt wird. Mit »-n« wird der Verzeichniswechsel verhindert, der Name des Verzeichnisses aber dennoch gespeichert. Dieser Stack kann rotiert werden, mit »-Anzahl« wird vom Ende des Stacks aus rotiert; mit »+Anzahl« vom Anfang.

Das nachfolgende Beispiel zählt alle Unterverzeichnisse ausgehend vom Startverzeichnis, wobei popd und pushd Verwendung finden:

user@sonne> cat countdir
#!/bin/sh

while :; do
  for i in $(ls); do
    test -d $i || continue
    pushd -n $(pwd)/$i &>/dev/null
    number=$(($number+1))
  done
  popd &>/dev/null && continue
  break
done
echo Anzahl: $number

pwd

Gibt das aktuelle Arbeitsverzeichnis aus. Mit der der Option »-P« wird der Pfad ohne enthaltene symbolische Links angegeben; mit »-L« werden Links berücksichtigt. Beide Optionen sind sinnvoll, um die Einstellung der Variablen »physical« zu überschreiben:

user@sonne> pwd
/usr/X11
user@sonne> pwd -P
/usr/X11R6
user@sonne> set -o physical
user@sonne> pwd
/usr/X11R6

read Variable [Variable]

Mit »read« wird eine Eingabezeile eingelesen und deren Inhalt Variablen zugewiesen. Die Eingabe wird anhand der in IFS vorgegebenen Trennzeichen in einzelne Token zerlegt und der Reihe nach den Variablen zugewiesen. Stehen mehr Token zur Verfügung als Variablen, so wird die letzte Variable mit allen noch nicht zugewiesenen Token belegt; stehen weniger Token bereit, bleibt der Inhalt der überschüssigen Variablen leer:

user@sonne> read a b c
1 2 3 4 5
user@sonne> echo "a=$a"; echo "b=$b"; echo "c=$c"
a=1
b=2
c=3 4 5

Ist die Anzahl erwarteter Token nicht vorhersehbar, bietet sich die Verwendung einer Feldvariablen an. Hierzu ist dem Variablennamen einzig die Option »-a« voranzustellen:

user@sonne> read -a feld
Jedes Wort gelangt in ein eigenes Feld.
user@sonne> echo "3.Element: ${feld[2]}"
3.Element: gelangt

Für die Shellprogrammierung sind zwei weitere Optionen nützlich. Zum einen »-p Prompt«, womit die erwartete Eingabe durch ein Prompt signalisiert wird und »-t Timeout«, wodurch das Kommando nach Ablauf der angegebenen Zeitspanne (Sekunden) mit einem Fehlerwert zurück kehrt:

user@sonne> read -p "Eingabe: " -t 5 || echo "nichts gedrückt"
# Finger weg von der Tastatur!
Eingabe: nichts gedrückt

readonly

Variablen und Funktionen lassen sich nachträglich als »nicht änderbar« deklarieren. Um eine einfache Variable nur-lesend zu vereinbaren, genügt die Angabe ihres Namens; bezieht man sich auf eine Funktion, so ist »-f Funktionsname« anzugeben. Eine Feldvariable bewahrt die Option »-a Feldvariable« vor versehentlichem Überschreiben. Wird »readonly« ohne Angabe von Optionen oder Variablennamen aufgerufen, erhält man eine Auflistung aller »read-only«-Variablen und -Funktionen.

return [n]

Dient zum Rücksprung aus einer Funktion. Mit [n] kann ein Rückgabewert vereinbart werden, ansonsten wird der Status des zuletzt innerhalb der Funktion ausgeführten Kommandos geliefert.

set

Dient zum Setzen bash-interner Variablen, die das Verhalten der Shell maßgeblich beeinflussen. Nähere Informationen wurden bereits weiter oben gegeben.

shift [n]

Dient zum Verschieben der Positionsparameter; ohne weitere Angaben wird die Liste der Positionsparameter um eine Stelle nach links verschoben; mit Angabe eine Ziffer wird um die spezifizierte Anzahl Stellen rotiert.

shopt

Dient zum Setzen bash-interner Variablen, die das Verhalten der Shell maßgeblich beeinflussen. Nähere Informationen wurden bereits weiter oben gegeben.

suspend

Suspendiert die Shell. Sie kann nur durch ein Signal SIGCONT reaktiviert werden.

test Ausdruck

test liefert in Abhängigkeit vom Wahrheitswert des Ausdrucks 0 (wahr) oder 1 (falsch) zurück und ist damit ein wichtiger Bestandteil vieler Shellskripten. Es existieren mannigfaltige Klassen von Tests. Doch bevor wir Ihnen eine Auswahl verfügbarer Tests vorstellen, möchten wir auf eine alternative Schreibweise hinweisen, die gern bei bedingter Ausführung eingesetzt wird. Im nachfolgenden Beispiel sind die beiden Zeilen semantisch äquivalent:

user@sonne> test -z $DISPLAY
user@sonne> [ -z $DISPLAY ]

Beachten Sie, dass nach der öffnenden eckigen Klammer und vor der schließenden zwingend ein Whitespace stehen muss!

Die wohl wichtigsten Tests befassen sich mit Dateien (alle Tests schließen einen Existenztest der Datei mit ein):

-b/-c

Test auf Gerätedatei (Block/Character):

user@sonne> test -b - /dev/hda; echo $?
0
user@sonne> test -b /dev/console; echo $?
1

-d

Test auf Verzeichnis:

user@sonne> for i in $(ls|head -5); do
>(test -d $i && echo "$i ist ein Verzeichnis")
>|| echo "$i ist kein Verzeichnis"
> done
Desktop ist ein Verzeichnis
Linuxfibel ist ein Verzeichnis
Systemprogrammierung ist ein Verzeichnis
allekapitel.htm ist kein Verzeichnis
amanda.rpm ist kein Verzeichnis

-e

Existenz der Datei

-f

Test auf normale Datei:

user@sonne> test -f /dev/hda; echo $?
1

-k

Test, ob das »sticky«-Flag auf ein Verzeichnis gesetzt ist:

user@sonne> test -k /tmp; echo $?
0

-p

Test auf Pipe:

user@sonne> test -p /dev/xconsole; echo $?
0

-r/-w/-x

Test auf Lese-/Schreib-/Ausführungsrecht

-s

Test, ob eine Datei nicht leer ist

-u

Test, ob das »suid«-Flag auf einer Datei gesetzt ist:

user@sonne> test -u /usr/bin/passwd; echo $?
0

Datei_1 -nt Datei_2 bzw. Datei_1 -ot Datei_2

Test, ob Datei_1 »neuer« bzw. »älter« ist als die Datei_2

Datei_1 -ef Datei_2

Test, ob Datei_1 und Datei_2 den selben Inode auf demselben Device besitzen (die eine Datei ist ein harter Link auf die andere):

user@sonne> ln bla foo
user@sonne> test bla -ef foo; echo $?
0

Des Weiteren existieren eine Reihe von Operatoren zum Vergleich von Zeichenketten:

-z Zeichenkette

Der Test ist wahr, wenn die Zeichenkette die Länge 0 hat

-n string

Der Test ist wahr, wenn die Länge der Zeichenkette >0 ist

Zeichenkette_1 == Zeichenkette_2

Wahr, wenn die Zeichenketten gleich sind

Zeichenkette_1 != Zeichenkette_2

Wahr, wenn die Zeichenketten ungleich sind

Zeichenkette_1 < Zeichenkette_2

Wahr, wenn die Zeichenkette_1 lexikalisch kleiner ist als Zeichenkette_2

Zeichenkette_1 > Zeichenkette_2

Wahr, wenn die Zeichenkette_1 lexikalisch größer ist als Zeichenkette_2

Eine vielseitige Anwendung ist der Vergleich von Argumenten mit den Operatoren -eq (gleich), -ne (ungleich), -gt (größer als), -lt (kleiner als), -ge (größer als oder gleich) und -le (kleiner als oder gleich):

# Überprüfung der Anzahl Parameter in einem Shellskript...
user@sonne> if [ "$#" -lt "3" ]; then echo "Zu wenige Parameter"; exit 1; fi

Anmerkung: Die Prüfung und Beendigung eines Skripts im Fehlerfall lässt sich eleganter über die Parametersubstitution realisieren:

# Elegante Überprüfung der Anzahl Parameter in einem Shellskript...
user@sonne> var_3 = ${3?-Zu wenige Parameter}

Mehrere Tests können kombiniert werden:

!

Negation

-a

Logisches UND zweier Tests

-o

Logisches ODER zweier Tests
user@sonne> test -b /dev/null -o -c /dev/null
user@sonne> test $? -eq 0 && echo "Gerätedatei"
Gerätedatei

times

Misst die Zeit für die Shell und der von dieser gestarteten Kommandos. Ausgegeben wird die User- und die Systemzeit (nicht jedoch die Realzeit, dies vermag das Kommando time):

user@sonne> times ls
0m0.090s 0m0.030s
0m0.160s 0m0.160s

trap

Viele Programme lassen sich während der Laufzeit über bestimmte Signale beenden oder unterbrechen (bspw. schießt [Ctrl]+[C] fast jedes Programm ab).

»trap« kann nun auf zwei Arten verwendet werden. Zum einen kann ein Signal (außer SIGKILL, Nr. 9) von der Behandlung durch die Bash ausgeschlossen werden. Dazu lässt man trap ein paar leerer Anführungsstriche und die Liste der zu ignorierenden Signalnummern (die Nummern erhält man mit "kill -l") folgen:

user@sonne> sleep 100
[Ctrl]+[C]   # Sofortiger Abbruch durch Signal SIGINT (Nr.2)

user@sonne> trap "" 2
# Die Bash interessiert sich nun nicht mehr für SIGINT: user@sonne> sleep 100
[Ctrl]+[C]   # Kein Abbruch

Die ursprüngliche Bedeutung eines Signals kann durch die Angabe von »trap Signalnummer« wieder hergestellt werden.

Die zweite Verwendung von »trap« erlaubt die Verbindung von Kommandos mit bestimmten Signalen. Trifft ein solches ein, wird das vereinbarte Kommando aufgerufen:

user@sonne> trap 'echo "SIGINT empfangen"' 2
user@sonne> [Ctrl]+[C]SIGINT empfangen

user@sonne>

trap ist somit für Shellskripte interessant, die mit temporären Dateien arbeiten, um diese bei Abbruch durch ein Signal ordnungsgemäß aufräumen zu können.

Die Option -p bringt alle von trap veränderten Signale mit ihrer Belegung zum Vorschein.

type

Das Kommando verrät »etwas mehr« über ein als Argument angegebenes Kommando. Ohne Option verrät es, ob das Argument ein builtin-Kommando, eine Funktion,... oder ein auf der Platte liegendes Programm ist:

user@sonne> type test
test is a shell builtin

user@sonne> type passwd
passwd is /usr/bin/passwd

Etwas bedeckt hält sich type -t, was die Art des Arguments mit einem knappen Wort erklärt:

user@sonne> type -t test
builtin

user@sonne> type -t passwd
file

-a lässt das Kommando alle Interpretationen ausgeben und ist bei der Suche nach einem Kommando sicher eine nette Hilfe:

user@sonne> type -a test
test is a shell builtin
test is /usr/bin/test

Schließlich beschränkt die Option -p die Suche auf die auf der Platte gespeicherten Programme, in vielen Distributionen hat sich dafür which eingebürgert:

user@sonne> type which
which is aliased to `type -p'

ulimit

Mit diesem Befehl können die von der Shell und aus dieser gestarteten Prozesse verfügbaren Ressourcen beschränkt werden. Der Administrator kann durch Verwendung des Kommandos in der /etc/profile einem jeden Benutzer, dessen Login-Shell die Bash ist, unwiderrufliche Schranken auferlegen. Der Benutzer kann diese Werte dann nur verringern; aber niemals erhöhen (dies darf einzig Root).

Jede Ressource kann durch zwei Werte beschränkt werden. Das Softlimit kann überschritten werden, während die Anforderung von Ressourcen, die das Hardlimit überschreiten mit einer Fehlermeldung abgewiesen werden. Softlimit setzt man mit der Option -L, das Hardlimit mit -H; ohne Option werden beide Limits mit ein und denselben Wert belegt.

Die Option -a bringt alle Ressourcen und die Softlimits zum Vorschein:

user@sonne> ulimit -a
core file size (blocks)     0           # -c
data seg size (kbytes)      unlimited   # -d
file size (blocks)          unlimited   # -f
max locked memory (kbytes)  unlimited   # -l
max memory size (kbytes)    unlimited   # -m
open files                  1024        # -n
pipe size (512 bytes)       8           # -p
stack size (kbytes)         unlimited   # -s
cpu time (seconds)          unlimited   # -t
max user processes          1024        # -u
virtual memory (kbytes)     unlimited   # -v

Hinter den einzelnen Ausgabezeilen haben wir die Optionen eingefügt, die Sie zur Manipulation eines konkreten Limits angeben müssen. Vielleicht ist zu dem einen oder anderen Limit noch eine Anmerkung notwendig, da vermutlich nicht jeder Leser ein Programmierer ist.

Eine core-Datei ist ein Speicherauszug, also ein Abbild des RAM. Linux schreibt den Speicherbereich eines Prozesses, falls jener mit einem schwerwiegenden Fehler abbricht (z.B. Speicherschutzverletzung) in eine Datei, die anschließend mittels eines Debuggers nach der Fehlerursache hin untersucht werden kann. Dazu bedarf es allerdings weit reichender Programmiererfahrungen. Da diese Cores durchaus mehrere MByte groß werden können, empfiehlt sich deren Anlegen zu unterbinden (Wert 0).

Zu jedem Prozess gehören neben dem im RAM liegenden Programm noch ein Datensegment für (der Name sagt's schon) die Daten und ein Stack, der u.a zur Wertübergabe zwischen Funktionen verwendet wird. Wer nicht genau weiß, was er tut, tut gut daran, mit diesen Limits nichts zu tun.

Die weiteren Werte sollten selbsterklärend sein.

Der Systemadministrator ist berechtigt, ein Limit aufzuheben, indem er an Stelle des Wertes das Schlüsselwort unlimited angibt.

Abschließend sei noch erwähnt, dass die vom System vorgegebenen Grenzen mit den hiesigen Werten nicht manipuliert werden können; dies geht nur durch Erzeugung eines neuen Kernels oder durch Änderungen einiger Parameter zur Laufzeit (siehe Prozessdateisystem).

umask

Setzt die Rechtemaske für neu erzeugte Dateien oder Verzeichnisse bzw. zeigt die Rechte an. Die Option -S veranlasst die Verwendung einer symbolischen anstatt der voreingestellten nummerischen Angabe:

user@sonne> umask -S
u=rwx,g=rx,o=rx

Weitere Beispiele finden Sie im Abschnitt Zugriffsrechte.

unalias

Löscht den angegebenen Alias bzw. mit der Option -a alle Aliasse.

unset

Dient zum Löschen einer Variable oder Funktion.

wait

Dient zum Warten auf einen oder alle Hintergrundprozesse. Wird keine Prozess- oder Jobnummer angegeben, so wird auf die Terminierung aller Hintergrundprozesse gewartet und der Rückgabewert ist 0. Mit Spezifizierung einer Nummer wird genau auf den betreffenden Prozess gewartet und dessen Rückgabestatus ist der Rückgabewert von »wait«.

Interaktive Bash Zurück Anfang Weiter

Editieren der Kommandozeile

Da sitzen Sie als Computerbändiger tagaus tagein vor der Klotze und dennoch trippeln ihre Finger bei weitem nicht so geübt über die Tastatur, wie eine erfahrene Sekretärin es vermag. Steigern Sie das Tempo, so schleichen sich ungewollte Fehler ein und die ganze Arbeit beginnt mit einem Kontrolllauf von vorn.

Ja die Eingabefehler, die bleiben nicht aus. Doch im Unterschied zur altehrwürdigen mechanischen Schreibmaschine bietet der Rechner endlose Chancen der Fehlerbereinigung.

Die Fülle der Navigationskommandos der Bash, um sich geschwind dem Fehler in der Eingabe anzunähern, sind so mannigfaltig, dass wohl die wenigsten Anwender ohne Nachzuschlagen, alle aufzählen könnten. Aber zum Glück genügt die Kenntnis einer kleinen Teilmenge, um den Cursor gekonnt in Position zu bringen. Bei entsprechender Konfiguration helfen die Nummern- und Navigationstasten der Tastatur schnell zum Ziel, jedoch sind diese in vielen Terminalemulationen nicht vorhanden, so dass Sie auf die »normalen« Navigationsmechanismen der Bash angewiesen sind.

Beginnen wir mit den Tastenkombinationen zum Positionieren des Cursors:

[Ctrl]+[a] bzw. [Ctrl]+[e]

Bewegt den Cursor an den Beginn bzw. das Ende der Zeile

[Ctrl]+[f] bzw. [Ctrl]+[b]

Bewegt den Cursor ein Zeichen vor bzw. zurück

[Alt]+[f] bzw. [Alt]+[b]

Bewegt den Cursor ein Wort vor bzw. zurück

[Ctrl]+[l]

Löscht den Bildschirm und schreibt die aktuelle Zeile erneut

Text kann gelöscht, eingefügt oder auf den gelöschten Text (in einem Zwischenpuffer) zugegriffen werden:

[Ctrl]+[d]

Löscht das Zeichen unter dem Cursor

[Ctrl]+[k]

Löscht den Text ab der Cursorposition bis zum Zeilenende

[Ctrl]+[u]

Löscht den Text links des Cursors bis zum Zeilenanfang

[Alt]+[d]

Löscht ab dem Cursor bis zum Ende des Wortes

[Ctrl]+[y]

Fügt den zuletzt gelöschten Text an der Cursorposition ein

[Alt]+[y]

Dieses Kommando kann nur unmittelbar nach [Ctrl]+[y] gerufen werden und rotiert den (Ring)Puffer der gelöschten Texte, dazu ein Beispiel:

user@sonne> echo Das Beispiel demonstriert das Löschen und Einfügen von Text_
# Cursor an den Anfang des Wortes "Einfügen"
[Alt]+[b],[Alt]+[b],[Alt]+[b]
user@sonne> echo Das Beispiel demonstriert das Löschen und Einfügen von Text
# Löschen von "Einfügen"
[Alt]+[d]
user@sonne> echo Das Beispiel demonstriert das Löschen und   von Text
# Cursor unter "Löschen" und löschen:
[Alt]+[b],[Alt]+[b],[Alt]+[d]
user@sonne> echo Das Beispiel demonstriert das   und  von Text
# Einfügen von "Einfügen" aus dem Ringpuffer:
[Ctrl]+[y],[Alt]+[y]
user@sonne> echo Das Beispiel demonstriert das Einfügen  und  von Text
# Einfügen von "Löschen" aus dem Ringpuffer hinter "und":
[Alt]+[f],[Ctrl]+[f],[Ctrl]+[y],[Alt]+[y]
user@sonne> echo Das Beispiel demonstriert das Einfügen und Löschen  von Text

Anmerkung: Der letzte Einfügevorgang funktioniert mit der angegebenen Tastenfolge nur, wenn einzig »Löschen« und »Einfügen« im Ringpuffer enthalten sind.

Ein paar Tastenkombinationen mögen dem Einen nützlich und dem Anderen widersinnig erscheinen...:

[Alt]+[u] bzw. [Alt]+[l]

Wandelt das Wort ab Cursorposition in Groß- bzw. Kleinbuchstaben um

[Ctrl]+[u]

Macht die letzte Änderung rückgängig

[Alt]+[r]

Macht alle Änderungen in der Kommandozeile rückgängig

[Alt]+[c]

Wandelt den Buchstaben an der Cursorposition in einen Großbuchstaben um und springt ans Ende des aktuellen Wortes

Die oben aufgeführten Eingabehilfen beschreiben einzig eine Teilmenge der Möglichkeiten der Bash. Und schon von diesen werden Sie sich die wenigsten verinnerlichen, oder?. Sind Sie an einer vollständigen Auflistung interessiert, so konsultieren sie das eingebaute Kommando »bind -P«.

Automatische Vervollständigung

Eine der größten Vorzüge der Bash gegenüber anderen Shells sind die umfangreichen Möglichkeiten, unvollständige Eingaben automatisch zu ergänzen. Die Bash ist somit in der Lage, sowohl Dateinamen, Kommandonamen, Benutzernamen, Rechnernamen als auch Variablennamen zu ergänzen, sofern die bisher eingegebenen Zeichen eine eindeutige Zuordnung zulassen.

Eine Variablennamensergänzung wird versucht, wenn das erste Zeichen ein $ ist; einem Benutzernamen muss die Tilde ~ und dem Rechnernamen ein @ voran stehen, um eine solche Ergänzung zu erzwingen. In allen anderen Fällen wird zunächst eine Kommando- und anschließend eine Dateinamensergänzung versucht.

Ein Beispiel: Um den Inhalt der Datei »/usr/doc/packages/quota/README« zu betrachten, geben Sie folgende Zeile ein:

user@sonne> less /usr/doc/packages/quota/README

Sie mussten 35 mal die Tastatur bemühen... Versuchen Sie nun die folgende Tastenfolge:

user@sonne> less /u[Tab]/d[Tab]/p[Tab]/q[Tab]/R[Tab]

Mit etwas Glück steht nun - nach nur 16 Tastaturanschlägen - dieselbe vollständige Kommandozeile zur Verfügung. Falls nicht, hat die Bash sicherlich mit einem Signal reagiert?

Die Bash ergänzt Ihre Eingabe nur, wenn beim Drücken der [Tab]-Taste die bislang eingegebene Zeichenkette eindeutig expandiert werden kann. Auf meinem System ist das bei obigem Beispiel der Fall, bei dem Ihren womöglich nicht.

Reagiert die Bash mit einem Signal, so erreichen Sie durch ein zweites Betätigen von [Tab] die Anzeige einer Liste aller Ergänzungen:

user@sonne> y[Tab] # die Bash reagiert mit einem Piepton
user@sonne> y[Tab][Tab]
yacc       ypcat       ypdomainname  ypwhich     yuvtoppm
ybmtopbm   ypchfn      ypmatch       yuvsplittoppm
yes        ypchsh      yppasswd      yuvsum

Kann die Zuordnung nicht vollzogen werden, müssen Sie weitere Zeichen eingeben und anschließend die [Tab]-Taste erneut bemühen:

user@sonne> yb[Tab]
user@sonne> ybmtopbm

user@sonne> finger @l[Tab]
user@sonne> finger @localhost

user@sonne> ls ~r[Tab]
user@sonne> ls ~root/

Die Tabulatortaste ist somit die nützlichste Eingabehilfe der Bash. Aber sie ist nicht die Einzige:

[Alt]-[?]

Zeigt alle Möglichkeiten der Ergänzung an (wie [Tab][Tab])

[Alt]-[*]

Fügt eine Liste aller möglichen Ergänzungen ein

[Alt]-[/]

Versucht eine Dateinamenergänzung

[Alt]-[$]

Versucht eine Variablenergänzung

[Alt]-[@]

Versucht eine Rechnernamenergänzung

[Alt]-[~]

Versucht eine Benutzernamenergä

[Alt]-[!]

Versucht eine Kommandonamenergänzung (Alias, Funktion, eingebautes Kommando, Programm)

Beispiel: Die Plattenplatzbelegung aller Benutzer soll überprüft werden. Benutzer, die weniger als 1k Speicherkapazität verwenden (0 Blöcke), werden ausgenommen:

user@sonne> du -s ~[Alt][*] 2>/dev/null | grep -v ^0
user@sonne> du -s ~adabas ~amanda ~at ~bin ~codadmin ~cyrus ~daemon ~db2as ~db2fenc1 ~db2inst1 ~dbmaker ~dpbox ~fax ~fib ~firewall ~fixadm ~fixlohn ~fnet ~ftp ~games ~gdm ~gnats ~informix ~ingres ~irc ~ixess ~lnx ~lp ~man ~mdom ~mysql ~named ~news ~nobody ~nps ~oracle ~postfix ~postgres ~root ~skyrix ~squid ~thomas ~user1 ~user2 ~user3 ~uucp ~virtuoso ~wwwrun ~yard ~zope 2>/dev/null | grep -v ^0
5270    /bin
11080   /sbin
39      /var/spool/lpd
1584    /var/cache/man
392240  /home/user

Umgang mit Prozessen

In Multitasking-Systemen arbeiten eine Menge von Prozessen quasi simultan. Etliche dieser Prozesse werden bereits beim Booten des Systems gestartet; ihrer Tätigkeit wird man auf einem schnellen Rechner kaum mehr gewahr.

Für gewöhnlich ist an einen Rechner nur ein Terminal - eine Einheit aus Ein- und Ausgabegerät - angeschlossen, somit kann (oder besser sollte) nur ein Prozess Eingaben vom Terminal empfangen und Ausgaben auf dieses schreiben. Der Benutzer kann nun entscheiden, ob er einen mit dem Terminal verbundenen Prozess startet oder jenen von jeglicher Ein- und Ausgabe entkoppelt. In letzterem Fall spricht man von einem Hintergrundprozess. Mit dem Hintergrundprozess ist keinerlei Interaktion möglich, es sei denn, er wird mittels Signalbehandlung manipuliert oder ihm wird nachträglich das Terminal zugeteilt.

Das Management von Prozessen bezeichnet man auch als Job-Kontrolle und alle modernen Shells bieten verschiedenen Mechanismen zu deren Unterstützung an.

Vordergrund- und Hintergrundprozesse

Rollen wir das Feld von hinten auf und beschreiben zunächst den Start eines Kommandos im Hintergrund. Ein simples Nachstellen des &-Zeichens genügt, um ein Kommando von der Standardeingabe zu entkoppeln und somit die Shell für neue Eingaben empfänglich zu machen:

user@sonne> less bla.txt &
[1] 814

[1]+  Stopped                 less bla.txt
user@sonne>

Ein Kommando wie less arbeitet bis zum expliziten Abbruch durch den Benutzer. Von dieser Interaktion ist es jedoch durch den Start im Hintergrund abgeschnitten, sodass in diesem Fall auch die Ausgabe des Kommandos unterdrückt wird. Würde man less bspw. durch cat ersetzen, würde der Inhalt der Datei bla.txt dennoch angezeigt werden, da cat seine Arbeit beenden und damit den Ausgabepuffer leeren würde.

Ist ein Kommando im Hintergrund wegen einer notwendigen Interaktion blockiert, vermag es erst weiter zu arbeiten, wenn die erwartete Eingabe erfolgte. Hierzu bedarf es jedoch der Standardeingabe. Anders gesagt, das Kommando muss in den Vordergrund geholt werden. Erreicht wird dies mit Hilfe von fg:

user@sonne> fg
Inhalt von bla.text
  bla.txt lines 1-1/1 (END)

Im Beispiel wurde fg ohne die Angabe einer Jobnummer verwendet, es bringt daher den aktuellen Job (i.A. der zuletzt gestartete und noch aktive Hintergrundprozess) zum Vorschein. Um die weiteren Zugriffmethoden zu demonstrieren, starten wir drei Prozesse im Hintergrund:

user@sonne> sleep 100& sleep 200& sleep 300&
[1] 1168
[2] 1169
[3] 1170
user@sonne> jobs
[1]   Running                 sleep 100 &
[2]-  Running                 sleep 200 &
[3]+  Running                 sleep 300 &

Das Kommando jobs zeigt eine Liste der derzeitigen Hintergrundprozesse (der Shell!) an. Verwenden Sie die Option -l, um zusätzlich die Prozessnummern zu sehen bzw. -p, wenn Sie sich einzig für die PIDs interessieren.

Der Zugriff auf den Job mit "sleep 200" kann nun über dessen Jobnummer oder den Kommandoaufruf erfolgen:

# Zugriff über die Jobnummer
user@sonne> fg %2
sleep 200

# Zugriff über den Kommandoaufruf
user@sonne> fg %"sleep 200"
sleep 200

Anstatt der Angabe des kompletten Kommandoaufrufs kann ein eindeutiger Bestandteil der Aufrufzeile zur Selektion dienen. Im Beispiel identifiziert "2" die Zeile "sleep 200" eindeutig, so dass Angaben wie fg %?2, fg %20 oder auch fg %200 ein und denselben Job in den Vordergrund bringen.

Des Weiteren holt fg %% bzw. fg %+ den zuletzt gestarteten Hintergrundprozess zurück auf die Konsole; fg %-1 beschreibt den vorletzten, fg %-2 den drittletzten,... gestarteten Hintergrundprozess.

Was nun kein Hintergrundprozess ist, muss ein Vordergrundprozess sein. Zu einem Zeitpunkt kann es nur einen im Vordergrund aktiven Prozess geben. Es ist genau der, der die Eingabe weiterer Kommandos an die Shell verhindert. Einen solchen Prozess können Sie auch nachträglich in den Hintergrund befördern. Hierzu muss er zunächst gestoppt werden, indem ihm das Signal SIGSTOP gesendet wird. In der Bash übernimmt die Tastenkombination [Ctrl]-[z] diese Aufgabe:

user@sonne> sleep 100
[Ctrl]-[z]

[4]+  Stopped                 sleep 100
user@sonne> jobs -l
[4]+  1235 Stopped              sleep 100

Um einen solchen gestoppten Prozess nun im Hintergrund weiter laufen zu lassen, bemüht man das Kommando bg. Analog zu fg ist der zuletzt gestoppte Prozess gemeint, wenn dem Kommando kein weiteres Argument folgt. Auch die Auswahl aus mehreren angehaltenen Prozessen folgt der im Zusammenhang mit fg beschriebenen Syntax.

Achtung! Die recht leichtfertige Sprachwahl vom "zuletzt gestoppten" oder "zuletzt im Hintergrund gestarteten" Prozess kann leicht verwirren, wenn sowohl Hintergrund- als auch angehaltene Prozesse koexistieren. In einem solchen Fall werden die gestoppten Prozesse VOR den Hintergrundprozessen eingereiht, d.h. die Kommandos greifen immer auf den zuletzt gestoppten Prozess zu!

user@sonne> sleep 100
[Ctrl]-[z]

[6]+  Stopped                 sleep 100
user@sonne> sleep 555&
[7] 1264
user@sonne> fg
sleep 100
user@sonne>

Ist das System überlastet?

Tummeln sich mehrere Benutzer zeitgleich auf einem Rechner und starten auch noch rechenintensive Programme, so erreichen CPU und/oder der Hauptspeicher bald ihre Grenzen. Das drastischste Mittel dem Andrang Einhalt zu gebieten, ist das Stoppen (bei reinem Mangel an CPU-Leistung) oder gar Beenden (wenn der Speicher erschöpft ist) einiger - für das System nicht zwingend notwendiger - Prozesse.

Mit den bislang besprochenen Möglichkeiten sollten Sie in der Lage sein, den aktuellen Vordergrund abzuschießen oder zu stoppen. Hintergrundprozesse könnten Sie erreichen, indem Sie sie in den Vordergrund holen und wie gehabt verfahren. Allerdings fehlt Ihnen weiterhin der Zugriff auf Prozesse, die nicht die aktuelle Shell als Vorfahren besitzen.

Signale sind die Nachrichten, die ein jeder Prozess versteht. Zahlreiche Signale besitzen vordefinierte Bedeutungen (siehe Allgemeines zu Shells, Prozesskommunikation), jedoch darf ein Programm diese ignorieren oder zu seinen Gunsten interpretieren. Das Signal SIGKILL (Nummer 9) führt stets zum Abbruch des betreffenden Prozesses (tatsächlich erreicht das Signal SIGKILL niemals den Zielprozess; der Kernel entfernt den Prozess einfach - sofern die Rechte stimmen).

Im Zusammenhang mit der Beeinflussung der aktiver Prozesse ist die Kenntnis dreier Signale nützlich:

  • SIGKILL (9) Der Prozess wird unmittelbar beendet
  • SIGSTOP (19) Der Prozess wird gestoppt
  • SIGTERM (15) Der Prozess wird "gebeten", sich zu beenden (ein Prozess hat die Chance, noch abschließende Arbeiten - Schließen von Dateien usw. - zu verrichten)

In der Bash existiert das eingebaute Kommando kill mit dem Signale an beliebige Prozesse gesendet werden können. kill erfordert die Angabe der Prozess- oder Jobnummern der betreffenden Prozesse sowie des zu sendenden Signals. Letzteres darf beim Signal SIGTERM entfallen, da dies die Voreinstellung ist.

user@sonne> sleep 1000&
[1] 1313
user@sonne> kill %1
user@sonne> bg
bash: bg: job has terminated
[1]+  Stopped               sleep 1000

Natürlich bedarf es zum Senden von Signalen an einen Prozess auch der Befugnisse:

user@sonne> kill -9 1 785
bash: kill: (1) - Not owner
bash: kill: (785) - Not owner

Die Prozessnummern erfahren Sie bspw. mittels dem Kommando ps; Jobnummern mit jobs.

An dieser Stelle sei noch erwähnt, dass ein Prozess nicht zwingend beendet werden muss, um seinen Rechenzeitverbrauch zu beschränken. Die Priorität eines Prozesses ist für die Zuteilung an CPU-Zyklen Ausschlag gebend, diese Kennzahl eines Prozesses kann sowohl bei dessen Start gesetzt, als auch zur Laufzeit modifiziert werden (nice bzw. renice). Da dies aber keine Eigenschaft der Bash ist, verweisen wir auf den Abschnitt Prozesssteuerung (Nutzerkommandos).

Verzögerte Ausgabe

Prozesse, die ihren Status wechseln, quittieren dies mit einer Ausgabe. Wie mit jeder Ausgabe verfahren wird, ist selbstverständlich konfigurierbar.

Das typische Verhalten der Bash ist die Benachrichtigung über den Statuswechsel unmittelbar vor der Anzeige eines neuen Prompts:

user@sonne> (sleep 1; echo foo)&
[1] 1372
user@sonne> foo
[Enter]

[1]+  Done                    ( sleep 1; echo foo )
user@sonne>

Durch diese Verzögerung "platzt" der Statusbericht nicht in die Ausgabe des aktuellen Vordergrundprozesses hinein. Gesteuert wird das Verhalten durch die Shelloption notify (Kommando set). Ist diese Option aktiviert (on), so erreicht uns die Statusmeldung sofort:

user@sonne> set -o notify
user@sonne> (sleep 1; echo foo)&
[1] 1376
user@sonne> foo
[1]+  Done                    ( sleep 1; echo foo )

Die History Zurück Anfang

Der Kommandozeilenspeicher

Der Kommandozeilenspeicher - kurz History - ist eine Liste, die die zuvor eingegebenen Kommandozeilen enthält. Um wie viele Einträge es sich maximal handelt, sagt die Shellvariable HISTSIZE; ist die Kapazität erschöpft, werden die ältesten Einträge gelöscht.

Die Erreichbarkeit und Eigenschaften der History werden weitest gehend über Bash-interne Variablen (siehe set und shopt) gesteuert. Um die nachfolgenden Ausführungen nachvollziehen zu können, sollten folgende Shellvariablen aktiv sein:

# History aktiv?
user@sonne> set -o | grep history
history         on

# History-Substitution aktiv?
user@sonne> set -o | grep histexpand
histexpand      on

Sicher haben Sie schon bemerkt, dass eine fehlerhafte Kommandozeile Ihnen nicht nochmals zum Editieren zur Verfügung steht. Indem Sie die Variable histreedit setzen (shopt -s histreedit) steht Ihnen die bemängelte Kommandozeile sofort zum Bearbeiten zur Verfügung.

History-Variablen

Neben den eingangs beschriebenen Variablen (die allesamt mit set oder shopt zu (de)aktivieren sind) wird der Kommandozeilenspeicher durch weitere Variablen beeinflusst:

HISTCMD

Der Index des aktuellen bearbeiteten Kommandos in der History-Liste steht hier. Diese Variable wird nur intern benutzt, um mit den später beschriebenen Kommandos in der History navigieren zu können.

HISTCONTROL

Über diese Variable lässt sich in gewissen Grenzen steuern, welche Eingabezeilen als Kandidaten für die Aufnahme in die Historyliste in Frage kommen.

Ist die Variable nicht gesetzt, oder steht irgend etwas außer "ignorespace", "ignoredups" oder " ignoreboth" drin, werden alle korrekten Eingabezeilen aufgenommen.

ignorespace schließt Eingaben aus, die mit einem Leerzeichen, Tabulator oder Zeilenumbruch beginnen.

ignoredups verhindert die Aufnahme von Eingaben, die genau so in der unmittelbar vorangegangenen Kommandozeile bereits erschienen.

ignoreboth schließlich kombiniert "ignorespace" und "ignoredups".

HISTIGNORE

Diese Liste kann eine Doppelpunkt-separierte Liste von Mustern enthalten. Eine Kommandozeile, die diesem Muster entspricht, wird von der Aufnahme in die History-Liste ausgeschlossen. Das Muster kann Metazeichen enthalten. Zusätzlich kann & als Platzhalter für den vorherigen Eintrag in der History-Liste verwendet werden.

Um bspw. alle Zeilen, die mit "echo" beginnen oder "ls" enthalten dem Kommandozeilenspeicher vorzuenthalten, muss die Variable wie folgt belegt sein:

user@sonne> export HISTIGNORE="echo*:*ls*"

histchars

Die weiter unten beschriebene History-Substitution wird vorgenommen, wenn die Eingabe mit einem bestimmten Zeichen beginnt (! oder ^). Mit dieser Variable können die voreingestellten Zeichen überschrieben werden. Soll das Ausrufezeichen (!) ausgetauscht werden, genügt die Belegung von histchars mit dem neuen Zeichen. Soll das Dach (^) verändert werden, muss das Ausrufezeichen (oder das Zeichen, das anstelle dessen verwendet wird) vorangestellt werden (also "!Zeichen").

HISTFILE

Diese Variable enthält den vollständigen Namen der Datei, in der die History zu speichern ist. Sie sollte immer gesetzt sein.

HISTFILESIZE

Wie viele Zeilen maximal die unter HISTFILE benannte Datei enthalten darf, wird hiermit fest gelegt. 500 ist ein typischer Wert.

HISTSIZE

Schließlich beinhaltet diese Variable die Anzahl maximal möglicher Einträge in der History. Diese Anzahl kann durchaus verschieden vom Wert in HISTFILESIZE sein, da letzteres nur bei Beendigung (oder expliziter Aufforderung) geschrieben wird.

Zugriff auf die Einträge des Kommandozeilenspeichers

Die einfachste Methode des Zugriff ist die Verwendung vordefinierter Tastenkombinationen:

Zurück bzw. Weiter

Zugriff auf das vorherige/nächste Element der History-Liste.

[Ctrl]+[P] bzw. [Ctrl]+[N]

Wie oben.

[Ctrl]+[R]

Suche rückwärts nach einer Zeile, die mit dem eingegeben Muster übereinstimmt:

user@sonne> [Ctrl]+[R]
# Nach Eingabe von "fi"
(reverse-i-search)`fi': chown :fibel bla
# Nach Eingabe von "fin"
(reverse-i-search)`fin': find /tmp -user user
# Nach Eingabe von "fin[CtrL]+[R]"
(reverse-i-search)`fin': find /root -name "*txt" 2>1

Durch wiederholte Eingabe von [Ctrl]+[R] wird die nächste Zeile geliefert, auf die das Muster zutrifft.

[Ctrl]+[S]

Suche vorwärts; Anwendung analog zu [Ctrl]+[R].

[Alt]+[<] bzw. [Alt]+[>]

Erstes bzw. letztes Element der History-Liste (der Zugriff auf das letzte Element ist selten konfiguriert)

Zugriff über die History-Substitution

Zwei Zeichen leiten eine Sonderform des Zugriff auf den Kommandozeilenspeicher ein. Dabei handelt es sich - sofern die Variable "histchars" nichts Gegenteiliges behauptet - um das Ausrufezeichen "!" und das Dach "^".

Leitet das Ausrufezeichen eine Kommandozeile ein und folgt diesem kein Whitespace, Punkt "." oder "=", so wird eine History-Substitution versucht. Folgt dem Ausrufezeichen eine Zahl, so wird die Kommandozeile aus dem Kommandozeilenspeicher entnommen und gestartet, die durch diese Zahl referenziert wird:

# Auf Zeile 100 steht...
user@sonne> history | egrep '^( )+100'
  100  grep myname /usr/dict/words

# Start des Kommandos mit Index 100:
user@sonne> !100
grep myname /usr/dict/words

Neben diesem absoluten Zugriff kann auch auf einen konkreten Eintrag ausgehend vom aktuellen Index verwiesen werden. Um n-letzte Kommando erneut auszuführen, tippt man ein Minus vor die Zahl:

# Das 10.-letzte Kommando:
user@sonne> !-10
history | egrep ^\ \ 100
  10  grep myname /usr/dict/words

# Zugriff auf das letzte Kommando:
user@sonne> !!
  10  grep myname /usr/dict/words

!! ist ein "abkürzende" Schreibweise für "!-1" (immerhin erspart das ein Zeichen).

Des Weiteren ist der Zugriff auf die letzte Kommandozeile, die ein bestimmtes (festes) Muster enthält, möglich. Die letzte mit einem Muster beginnende Zeile wird durch !muster hervor gerufen; eine das Muster enthaltende Zeile bringt !?muster? zum Vorschein:

user@sonne> !echo
echo $PATH
/usr/sbin:/bin:/usr/bin:/sbin:/usr/X11R6/bin

user@sonne> !?grep?
history | egrep ^\ \ 100
  10  grep myname /usr/dict/words

Mit dem Ausrufezeichen ist sogar der Zugriff auf die einzelnen Argumente des letzten Kommandos möglich: !:Argumentnummer. Um bspw. den Typ des letzten Kommandos fest zu stellen (der Kommandoname ist das 0. Argument), hilft folgende Eingabe:

user@sonne> type !:0
type mv
mv is hashed (/bin/mv)

Das letzte Argument ist mittels "!:$" abrufbar.

Damit nicht genug... der Ausdruck !* bzw. !:* bietet Zugriff auf die gesamte Liste der Argumente des zuletzt ausgeführten Kommandos:

user@sonne> echo "Die letzten Argumente lauteten " !*
echo "Die letzten Argumente lauteten " | egrep ^\ \ 100

Letztlich handelt es sich im letzten Beispiel nur um einen Spezialfall der Bereichsangabe, die mit !:n-m die Argumente n, n+1, ..., m zurück holt:

user@sonne> echo Damit auch ein paar Argumente existieren...
user@sonne> echo !:3-5
echo ein paar Argumente
ein paar Argumente

Als Krönung des Ganzen lassen sich die vorgestellten Substitutionen kombinieren, so dass Sie auch Zugriff auf die Argumente früherer Kommandoaufrufe erlangen:

user@sonne> finger tux@erde user@sonne alf@irgendwo
# Weitere Kommandoaufrufe
user@sonne> mail !finger:*
mail tux@erde user@sonne alf@irgendwo
Subject:

In vielen Situationen wird man eine Kommandozeile mit einem veränderten Argument erneut aufrufen. Anstatt die Kommandozeile zu editieren, kann die letzte Kommandozeile ausgeführt werden, wobei ein Teil dieser ersetzt wird. Man erreicht dies durch Substitution mittels ^Altes_Muster^Neues_Muster^:

user@sonne> find /usr/share/doc -name "README"
...
user@sonne> ^README^LIESMICH^
find /usr/share/doc -name "LIESMICH"
...

Manipulation der History-Einträge mit fc

Die bisherige Handhabung des Kommandozeilenspeichers zielte auf den Zugriff eines konkreten Eintrages ab, um diesen, ggf. nach vorheriger Manipulation auszuführen. Es konnte zu einem Zeitpunkt genau eine Kommandozeile editiert werden, wobei die Bearbeitung mit den Mitteln der Bash erfolgte.

Eine Alternative, mit der auch mehrere Kommandos aus der History mit einem beliebigen Editor bearbeitet werden können, bietet das eingebaute Kommando fc. Nach Beendigung des Editors werden das/die Kommando(s) gestartet (eine Ausnahme bildet die Option -l).

Um welchen Editor es sich handelt, kann durch die Variable FCEDIT fest gelegt werden. Ist sie nicht oder falsch belegt, so wird die Variable EDITOR konsultiert. Verweist auch deren Inhalt auf keinen existierenden Editor, wird der vi gestartet. Eine Angabe eines Editors per Kommandozeilenoption (-e Editor) genießt Vorrang vor den obigen Editor-Angaben.

Die Option -l schreibt die Ausgabe von fc auf die Standardausgabe. Es ist der einzige Fall, in dem die Kommandos nicht ausgeführt werden. Um bspw. die letzten acht Kommandos in der History zu betrachten, hilft folgender Aufruf:

user@sonne> fc -l -8
494      vi bash.htm
495      vi register.htm
496      mv bash.htm.old ~/trash
497      ./check_structure
498      talk tux
499      r cd
500      date
501      fc -e vi 6 12

Die Nummerierung entspricht dem Eintrag eines Kommandos im Kommandozeilenspeicher und kann durch Angabe der Option -n unterdrückt werden. Bezug nehmend auf diese Nummerierung kann auch ein gezielter Bereich aus der History herausgezogen werden, indem dieser als "von bis" angegeben wird. Wir verzichten nachfolgend auf die Umleitung zur Standardausgabe, womit die gefundenen Zeilen einem Editor zugeführt werden:

user@sonne> fc 496 499
496      mv bash.htm.old ~/trash
497      ./check_structure
498      talk tux
499      r cd ~
~
~
~
"/tmp/bash977420496" 4L, 48C                     1,1          A

Sie können die Kommandos nachfolgend editieren oder Löschen. Mit Abspeichern des Resultats wird versucht, jede der Zeilen auszuführen. Möchten Sie also keines der Kommandos starten, dann entfernen Sie sie aus dem Editor. Der Kommandozeilenspeicher bleibt davon unberührt.

Aber wer merkt sich schon die Zahlen? Intuitiver wäre da eine Angabe der Art: "Alle Kommando zwischen der Kommandozeile, die mit jenem Muster begann und jener, die mit diesem Muster startete.". Auch das ist möglich:

user@sonne> fc -l "vi b" tal
494      vi bash.htm
495      vi register.htm
496      mv bash.htm.old ~/trash
497      ./check_structure
498      talk tux

Die Angabe zu bearbeitender Bereiche kann demnach anhand der Nummer des Historyeintrags oder anhand von Mustern, mit denen eine Kommandozeile begann, erfolgen. Ebenso ist die Kombination aus Muster und Nummer erlaubt. Um auf das n-letzte Kommando zuzugreifen, wird dessen Nummer mit einem vorangestellten Minus ("-5", "-100") angegeben. Die letzten drei Kommandos erreicht man nach diesem Schema mit:

user@sonne> fc -l -1 -3
497      ./check_structure
498      talk tux
499      fc -l "vi b" tal

Wird auf eine Bereichsangabe verzichtet, so zeigt fc -l die letzten 17 History-Einträge an.

Bei der Arbeit mit der Bash geschieht es immer wieder, dass man eine Kommandozeile mehrfach auszuführen gedenkt, wobei bei jedem Durchlauf ein Muster durch ein anderes zu ersetzen ist. Findige Benutzer denken dabei gleich an eine "for"-Schleife über die erwünschten Muster, was sicherlich eine Lösung wäre. Das Kommando fc bietet in Kombination mit der Option -s einen Mechanismus, der genau dasselbe realisiert.

Als Beispiel sollen 3 Dateien (chapter1, chapter2, chapter3) umbenannt werden. Für die erste Datei bemühen wir das Kommando mv. Für die folgenden nutzen wir die Substitution von Mustern durch das Kommando fc:

user@sonne> mv chapter1 ~/trash/chapter1.bak
user@sonne> fc -s 1=2
mv chapter2 ~/trash/chapter2.bak
user@sonne> fc -s 2=3
mv chapter3 ~/trash/chapter3.bak

Bei solchen kurzen Kommandozeilen mag das manuelle Anpassen der Zeilen schneller vonstatten gehen, aber für komplexe Eingaben ist fc eine nützliche Hilfe.

Im letzten Beispiel wurde kein Kommando angegeben, sodass fc die letzte Kommandozeile bearbeitete und startete. Auf einen zurückliegender Kommandoaufruf ließe sich durch Angabe eines eindeutigen Musters zugreifen:

user@sonne> cd /usr/share/doc/howto
# weitere Kommandoaufrufe...
user@sonne> fc -s share="" cd
cd /usr//doc/howto

Beachten Sie, dass das Kommando im Beispiel den letzten Aufruf von "cd" betrifft, unabhängig davon, ob in dessen Argumenten das Wort "share" auftauchte. Wird ein zu ersetzendes Muster nicht gefunden, wird nichts ersetzt; es ist aber kein Fehler!

Speziell in der Kornshell wird häufig ein Alias r='fc -s' verwendet. Tippt man irgendwann r auf die Kommandozeile, so wird das letzte Kommando wiederholt ausgeführt. Gibt man jedoch bspw. r cd ein, so wird die letzte mit "cd" beginnende Kommandozeile gestartet. Möglich ist dies, da die Musterersetzung entfallen darf.

Korrekturen, Hinweise?
Startseite Nächste Seite Nächstes Kapitel Vorherige Seite Kapitelanfang