Anfang
Inhalt
Einleitung
Erste Schritte
Die Bash
Das Dateisystem
Nutzerkommandos
Installation
Shells
Allgemeines
Bash
Bashprogrammierung
Csh
Csh-Programmierung
Ksh
Ksh-Programmierung
Unix-Werkzeuge
System-Administration
X Window System
Der Kernel
Netzwerk Grundlagen
Netzwerk Clients
Netzwerk Server
Netzwerk Sicherheit
Anhang
Register
|
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.
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. |
Abbildung 1: Unterteilung der Bash
Interaktive und Nicht-Interaktive Bash
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
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.
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
...
|
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)!
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.
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:
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 |
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 rekapituliert.
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 |
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:
- Tildeexpansion
- Parameter- und Variablenexpansion
- Kommandosubstitution
- Arithmetische Substitution
- 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.
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:
- Variablenzuweisungen und Ein-/Ausgabe-Umleitungen werden markiert (damit sie nicht
im folgenden Schritt betrachtet werden)
- Alle nicht-markierten Worte werden expandiert (siehe nachfolgend)
- Ein- und Ausgabe-Umleitungen werden vorbereitet (indem die entsprechenden
Dateideskriptoren geschlossen/geöffnet werden)
- Die Variablenzuweisungen werden vorgenommen (inklusive vorheriger Expansionen)
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:
- Klammernexpansion
- Tildeexpansion
- Parameter- und Variablenexpansion
- Kommandosubstitution
- Arithmetische Substitution
- Wortzerlegung
- Pfadnamensexpansion
Was sich hinter den einzelnen Expansionen verbirgt, soll im Anschluss an diesen
Abschnitt betrachtet werden.
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 |
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 -) |
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 -x 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 |
|
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:
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:
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 |
|
Eine Beispiel zur Anwendung wird im Zusammenhang mit dem eingebauten Kommando exec gegeben.
|
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.
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> |
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!
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 |
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 |
Die Kommandosubstitution erlaubt das Ersetzen ihres Aufrufes durch ihre Ausgabe. Es
existieren zwei Syntaxvarianten des Aufrufs:
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 |
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 |
|
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 |
|
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.
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 |
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/ |
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.
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.
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 |
|
Siehe Prompts |
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).
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 |
|
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:
- Primäres Prompt: Dieses zeigt an, dass die Bash eine
neue Eingabe erwartet.
-
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> |
- Drittes Prompt: (nur bash) Dieses Prompt erscheint bei der interaktiven
Arbeit mit dem builtin-Kommando select und ist über
die Shellvariable "$PS3" konfigurierbar.
-
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.
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.
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:
-
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 |
- Die Funktion muss vor ihrer Verwendung definiert sein.
-
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 |
-
Wird eine Funktion mittels »exit« verlassen, wird auch der rufende
Prozess beendet:
user@sonne> func(){ exit; }
user@sonne> func
login: |
-
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 |
-
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.
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:
- Weil es ein solches Kommando in Unix nicht gibt (Beispiel "source")
- Weil ein builtin Kommando effizienter arbeitet, als ein externes Kommando (keine
Prozesserzeugung notwendig; Beispiel »echo«)
- Weil nur ein eingebautes Kommando Bash-interne Variablen ändern kann (Beispiel
»export«)
- 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.
»ü« 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
'"ü":"ü"' |
Sobald Sie »ü« tippen, erscheint in der Eingabe »ü«. 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 Nutzerkommandos Dateien 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:
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 |
|
Siehe unter Manipulation der History-Einträge
|
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«. |
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änzung |
[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 )
|
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:
bzw.  |
|
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"
... |
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.
|
|