Jobs
Die Bash, das unbekannte Wesen
Von Befehlsfolgen und -blöcken zur Alias-Definition
Im letzten Teil dieser Serie wurden zwei Möglichkeiten erläutert, Befehle (via Pipes oder spezieller Shell-Operatoren) nacheinander als Befehlsfolge ausführen zu lassen. Grundsätzlich muß dabei unterschieden werden, ob die Befehle von der momentan aktiven Shell ausgeführt werden oder ob diese die Befehlsfolge an eine zweite Shell (die sogenannte Subshell) weiterreicht. Im ersten Fall können die Befehle Veränderungen an der ausführenden Shell vornehmen (etwa bestimmte Umgebungsvariablen verändern), im zweiten Fall ist dies nur indirekt möglich. Die Bash verwendet daher zwei unterschiedliche Syntax-Varianten für diese beiden Möglichkeiten:
$> ( cd ; find . -print > find.tmp ; less find.tmp ;)
$> { cd ; find . -print > find.tmp ; less find.tmp ;}Die schließende Klammer wird bei dieser Schreibweise analog zu einem Befehlsnamen behandelt. Sie muß daher entweder nach einem Semikolon oder als erstes Wort in einer neuen Zeile stehen.
Die unterschiedlichen Effekte werden in diesem einfachen Beispiel deutlich: Im ersten Fall (Ausführen der Befehle in einer Subshell) ist am Ende der Bearbeitung das aktuelle Verzeichnis unverändert, im zweiten Fall dagegen nicht. Ähnlich verhält es sich mit den Umgebungsvariablen, Alias-Definitionen und Funktionen: In der ersten Variante gehen Änderungen beim Beenden der Befehlsfolge verloren, im zweiten Fall bleiben sie bestehen. Ein einfaches Beispiel dazu. Unter einem (Befehls-)Alias versteht man eine mit dem Bash-internen alias-Befehl definierte Abkürzung, die analog zu "normalen" Befehlen ausgeführt werden kann. Üblich sind beispielsweise folgende Alias-Definitionen:
$> alias al=alias $> alias ll='ls -l' $> alias df='df -h' $> al cp='cp -iv ' $> al rm='rm -iv ' ...
Im ersten Beispiel wird eine Abkürzung für den Alias-Befehl selbst definiert. Anstelle des ausgeschrieben Befehlsnamen alias kann nun al verwendet werden, wie dies in der vierten Zeile erfolgte. In der zweiten Zeile erfolgt die Deklaration eine der am häufigsten überhaupt verwendeten Alias-Definitionen. (Viele User glauben, ll sei ein eigenständiger Befehl. Das stimmt aber nicht.) Im dritten, vierten und fünften Beispiel werden Definitionen für bereits bestehende Befehlsnamen deklariert. Da die Bash Alias-Definitionen nicht rekursiv auswertet, ist dies zulässig. Der Effekt ist dabei folgender: Die Alias-Definition "maskiert" den eigentlichen Befehl, da die Bash immer zunächst prüft, ob für einen Befehlsnamen ein Alias besteht, bevor sie versucht, einen externen Befehl mit eben dem Namen auszuführen. Andere Shells verhalten sich übrigens analog. Ruft der Anwender also df auf, findet die Bash die Alias-Definition df -h (-h steht für ein durch Menschen lesbares Ausgabeformat "human readable") und führt den entsprechenden Befehl aus. Bei der Deklaration eines Alias muß die rechts vom Gleichheitszeichen stehende Definition in Hochkommata eingefaßt werden, wenn sie Leerzeichen enthält. Auf diese Weise werden, wie oben gezeigt, oft destruktive Befehle in ihrer Wirkung "entschärft". Diese Varianten von cp und rm fragen nach, bevor sie bestehende Dateien überschreiben.
Einige Anmerkungen zum Einsatz von Alias-Definitionen: Eine Alias-Definition wird von der Bash nur dann erkannt, wenn sie in der Befehlszeile dort auftritt, wo normalerweise Befehlsnamen erwartet werden, also beispielsweise als erstes Wort. Nach einem Befehl wie nohup erkennt die Bash eine Alias-Definition nicht als solche. Was ist nun aber, wenn, wie oben beschrieben, ein Befehl mit einer Alias-Definition maskiert wurde, aber seine ursprüngliche Form eingesetzt werden soll? Entweder geben Sie dann den Befehl mit seinem Pfad ein, oder Sie maskieren die Alias-Definition in der Befehlszeile mit einem Backslash-Zeichen:
$> /bin/df $> \df
Alias-Definitionen weisen noch weitere Besonderheiten auf: Nur wenn sie mit einem Leerzeichen enden (wie oben in den Beispielen vier und fünf ) prüft die Bash, ob es sich bei dem auf ein Alias folgenden Wort ebenfalls um ein Alias handelt. Die Bash bearbeitet ("expandiert" heißt das im Fachjargon) den auf der rechten Seite einer Alias-Definition stehenden Code bei der Deklaration und nicht erst beim Ausführen des Alias. Manchmal ist es wichtig, dies zu berücksichtigen. In einer nicht-interaktiven Shell werden Alias-Definitionen gar nicht expandiert.
Alle aktuell deklarierten Alias-Definitionen werden angezeigt, wenn der alias-Befehl ohne weitere Argumente aufgerufen wird.
$> alias ... alias search='grep *.tex -e' alias searcha='agrep -B ' alias searchr='rgrep -nhB ' ...
Eine Alias-Definition kann durch den unalias-Befehl gelöscht werden.
$> unalias searchr $> searchr bash: searchr: command not found
Die Bash kennt nur Alias-Definitionen ohne Parameter (Argumente). In den meisten Fällen ist dies ausreichend, da die wichtigsten Befehle bereits eine flexible Verwendung der Optionen und Argumente ermöglichen. Sogar der etwas störrische grep-Befehl kann in einem Alias eingesetzt werden, nutzt man diese Fähigkeiten aus:
$> grep Optionen -ef Muster Pfad $> alias search='grep *.tex -e' $> search grep bash-jobs.tex:Sogar der etwas störrische grep -Befehl bash-jobs.tex:$> alias search='grep *.tex -e' bash-tips.tex:history grep bash-tips.tex:alias hg='history | grep '
search sucht in diesem Beispiel nur in den relevanten Tex(t)dateien nach dem angegeben Stichwort, alle anderen Files bleiben unberücksichtigt. Noch eleganter wird die Suche, wenn man spezielle grep-Varianten verwendet:
$> alias searcha='agrep -B ' $> alias searchr='rgrep -nhB '
Mit agrep kann "unscharf" nach einen Stichwort gesucht werden, Treffer müssen dann nicht mehr exakt benannt und angegeben werden, sondern können mit Fehlern behaftet sein. Die Option -B (best) sucht automatisch die besten Ergebnisse heraus.
rgrep erlaubt die rekursive Suche und zusätzlich ein Hervorheben der Treffer:
$> searcha grtp *tex agrep: 2 words match within 1 error; search for them? (y/n)
$> searchr grep ... bash-jobs.tex:416:$> search grep ...
Um auch für diese grep-Varianten die Zugriffe auf die relevanten Dateien einzuschränken, bedarf es eines etwas größeren Aufwands. Hierfür werden dann Funktionen eingesetzt.
Funktionen
Funktionen sind kleine Befehlsgruppen, die als funktionelle Einheit von der Bash temporär gespeichert werden. Ihre Deklaration wird mit dem Schlüsselwort function eingeleitet, gefolgt von zwei runden Klammern. Der eigentliche funktionelle Code steht zwischen geschweiften Klammern. Er endet mit einem Semikolon.
$> function Funktionsname
() {" "Code
;" "}
Das Schlüsselwort function ist übrigens optional, es wird aber oft zum besseren Verständnis angegeben. Wichtig sind die durch " " gekennzeichneten Leerzeichen. Längere Funktionsdeklarationen erfolgen nicht mehr in einer Zeile, sondern in einer Blockform:
$> function Funktionsname
()
>{
> zeilenweiser Code, oft eingerückt
>}
Bei dieser Eingabeform tritt nach der ersten Zeile ein spezieller Prompt auf: ">", der anzeigt, dass die Deklaration syntaktisch noch nicht abgeschlossen ist.
Ein wesentliches Merkmal von Funktionen sind ihre Parameter. Hier werden zunächst nur die sogenannten "Positionsparameter" (positional parameters) betrachtet. Ein Positionsparameter zeichnet sich nur durch seine Stellung in der Befehlszeile - also relativ zur Funktion - aus. Die Positionsparameter werden entsprechend von links nach rechts durchnumeriert. Auf jeden Positionsparameter kann durch seine Nummer Bezug genommen werden (er wird "referenziert"). Dazu wird diese Nummer nach einem Dollar-Zeichen angegeben:
$> kg () { echo $0 $1 $2 $1 $3 $4 $5 $6 $7 $8 $9 $10 $11; }
$>kg a b c d e f g h i j k l
bash a b a c d e f g h i a0 a1
Das Beispiel zeigt, dass die ersten zehn (0 bis 9) Parameter offenbar problemlos erkannt werden, wenn $0 auch eine besondere Bedeutung zukommt. Um elf oder mehr Parameter einer Funktion referenzieren zu können, müssen die Positionsnummern in geschweiften Klammern angegeben werden:
$> kg () { echo $0 $1 $2 $1 $3 $4 $5 $6 $7 $8 $9 ${10} ${11}; }
$>kg a b c d e f g h i j k l
bash a b a c d e f g h i k lMit den Positionsparametern können wir nun die Alias-Definitionen von oben verbessern:
$> function searcha () { agrep -B $1 *.tex; }Für die rekursive Variante sieht es entsprechend aus:
$> function searchr () { rgrep -nhB $1 *.tex; }Man kann das sogar noch schöner machen, wie in einer der nächsten Folgen gezeigt wird.
Neben dem Parameter $0, der immer das Nullte Argument einer Befehlszeile enthält - oder aber "bash" bei der Ausführung von Funktionen und Scripts -, gibt es noch eine Reihe weiterer spezieller Parameter. Diese werden hauptsächlich im Zusammenhang mit Funktionen und Scripts bedeutsam, stehen aber ebenfalls für eine interaktive Nutzung zur Verfügung.
Tabelle 1:Spezielle Parameter der Bash
| Typ | Funktion |
|---|---|
$*
|
Dies wird durch den Inhalt aller in der Befehlszeile folgenden Positionsparameter ersetzt. Als Trennzeichen zwischen den Argumenten wird normalerweise ein Leerzeichen gesetzt. $* -> $1" "$2" "...
|
"$*"
|
In dieser Form werden die Positionsparameter ohne Trennzeichen expandiert."$*"-> "$1$2..."
|
$@
|
Alle Positionsparameter ab dem ersten ($1) werden expandiert. Wird dieser Parameter in Hochkommata verwendet, expandiert die Bash die Positionsparameter einzeln in Hochkommata: "$@"-> "$1""$3""$2"...
|
$#
|
Expandiert zu der dezimal angegeben Nummer von Positionsparametern. |
$?
|
Expandiert den Rückgabewert (Return Code) des zuvor ausgeführten Befehls in der Befehlszeile. |
$$
|
Die Bash ersetzt dies durch die PID der aktuellen Shell. Bei einer Subshell wird die PID der aufrufenden Shell expandiert. Dieses Feature wird gern zum Erzeugen einmaliger Dateinamen verwendet. |
$!
|
PID des letzten Hintergrund-Jobs. |
$0
|
Name des aktuellen Befehls oder Scripts. Bei Funktionen: bash.
|
$_
|
Absoluter Pfad des aktuellen Befehls oder Scripts beim Aufruf der Shell. |
Alle zur Zeit deklarierten Funktionen können durch den eingebauten declare-Befehl mit der Option -f angezeigt werden:
$> declare -f
...
declare -f searcha ()
{
agrep -B $1 *tex
}
declare -f searchr ()
{
rgrep -nHB $1 *tex
}
declare -f searchr ()
{
rgrep -nHB $1 *
}
...
Um nur die Funktionsnamen darzustellen, wird die Option -F verwendet.
ACHTUNG: Die Bash findet eine Alias-Definition immer vor einer gleichnamigen Funktionsdeklaration, sogar vor den eingebauten Shell-Befehlen. Achten Sie daher auf eindeutige Namen und verwenden Sie auch keine Schlüsselwörter und Befehlsnamen (etwa test, oder "y.", help, jobs, kill, etc.) als Funktions- und Aliasnamen, es sei denn, Sie wollen deren Funktion neu definieren. Mittels type kann festgestellt werden, wie die Bash ein Befehlswort interpretiert:
$> type searcha
searcha is a function
searcha ()
{
agrep -B $1 *tex
}
Gelöscht werden Shell-Funktionen übrigens mit dem unset-Befehl:
$> unset Funktionsname
Funktionen können rekursiv verwendet werden, dürfen sich also in ihrer Deklaration selbst aufrufen.
$> kg(){ echo :kg:$SECONDS ; kg; }
$> kg
:kg:170540
:kg:170541
:kg:170541
:kg:170541
...Hier zeigt sich, wie schnell Funktionen sein können, da sie direkt im Speicher vorgehalten werden.
$> function kg(){ let i=$i+1 ; echo $">i ; kg; }
$> kg
...
2761
2762
2763
2764
2765
...



