Das vorliegende Material versucht, einen relativ umfangreichen Überblick über die Philosophie und den Leistungsumfang der Bash und vielfach allgemein der Unix-Shell-Schnittstelle zu geben, der auch bei der Verwendung anderer Shells (speziell der Korn- oder Z Shell) hilfreich sein kann. Es geht dabei nicht um den Ersatz der sehr umfangreichen, für die Klärung von Detail-Fragen meist unverzichtbaren Bash-Manuals, sondern um eine Ergänzung, die auf wichtigste oder typische Punkte hinweist und vielfach versucht, diese an instruktiven Beispielen (die in den Manuals kaum zu finden sind) zu erläutern.
Der Text soll dem Shell- bzw. Bash-Neuling den Einstieg in diese Materie erleichtern sowie dem Shell-Kenner das schnelle Nachschlagen spezieller Bash-Konstrukte ermöglichen und ihm vor allem auch die neuen Möglichkeiten der Bash aufzeigen, die bei der älteren Bourne-Shell noch nicht existierten und deren schrittweise Aneignung jedem aktiven Shell-Nutzer nur dringend empfohlen werden kann, da sie seine Arbeit deutlich effektivieren können.
Für Details, die hier nicht dargestellt sind, sei generell auf das Online- und das Referenz-Manual der Bash sowie geeignete Zusatz-Literatur verwiesen, die in Form gedruckter Bücher sowie freier Web-Seiten zur Verfügung steht. Hinweise dazu findet man auf den Seiten Links zu Bash, Z Shell, Shell-Programmierung etc. und Literaturempfehlungen zu Bash und Shell-Programmierung.
Unter Unix/Linux erreicht man die beiden Manuals in der Regel über die folgenden Kommandos:
man bash
info bash
Das Referenz-Manual existiert neben dieser einfachen Text-Form auch noch in den Formaten HTML, PDF, PS und DVI.
In der Regel benötigt man mehrere solche Dokumente, um vor allem knifflige Fragen zu klären, da moderne Shells über einen sehr großen Funktionsumfang verfügen, der vermutlich in keinem Dokument komplett dargestellt wird. Außerdem ist es oft interessant, die Sichtweise verschiedener Autoren zu einem Thema kennenzulernen. Generell ist es auch sehr lehrreich, bestimmte Antworten durch eigene Experimente mit der Shell zu finden.
Nachfolgend wird primär der Leistungsumfang der Bash-Versionen 2.X und 3.X dargestellt, wobei auf eine Auswahl der neuen Möglichkeiten (Features) der Bash 3 in einem eigenen Abschnitt gesondert hingewiesen wird.
Die Bash ist kompatibel zur Bourne-Shell und bietet zusätzlich viele nützliche Eigenschaften von ksh (Korn-Shell) und tcsh (TENEX C-Shell), bringt aber auch neue (Bash-spezifische) Elemente ein. Sie ist wie die zsh (Z Shell) eine leistungsfähige interaktive und nicht-interaktive Shell. Die Bash beabsichtigt, eine POSIX-konforme Implementierung gemäß POSIX 1003.2 (IEEE POSIX Shell and Tools specification) zu sein.
Die Bash steht auf einer Vielzahl von Plattformen zur Verfügung, u.a. für Linux, verschiedene Unix-Versionen und Windows. Ein Teil der Aussagen und Beispiele des vorliegenden Materials sind Unix-/Linux-spezifisch. Die anderen gelten generell für alle Plattformen.
An einigen Stellen wird im Text auch auf Unterschiede zu anderen Shells, speziell zur tcsh und zsh, hingewiesen, da diese beiden neben der Bash die am häufigsten genutzten (interaktiven) Shells sein dürften. Um langjährigen tcsh-Kennern (zu denen auch der Autor dieser Seite gehört) einen evtl. Umstieg auf die Bash zu erleichtern, wurde für sie ein eigener Abschnitt aufgenommen, der auf einige deutlich spürbare Unterschiede zwischen Bash und tcsh hinweist.
Es sei noch erwähnt, dass die zsh von den hier genannten Shells den größten Leistungsumfang bietet und sich einer zunehmenden Beliebtheit bei Profi-Shell-Nutzern erfreut. Sie umfasst weitgehend komplett die Funktionalität der Bash und der Korn-Shell (wenn auch teilweise mit anderen syntaktischen Mitteln bzw. anderer Ausprägung) und unterstützt gezielt viele Eigenschaften der tcsh. Zusätzlich verfügt sie über spezielle zsh-Features, die man in keiner anderen Shell findet. Dazu gehören z.B. erweiterte Möglichkeiten beim sog. Globbing (also der Nutzung von Mustern zur Pfadnamens-Expandierung), ein implizites tee (die gleichzeitige Ausgabe-Umlenkung in mehrere Dateien) und ein mehrzeiliger Kommandozeilen-Editor mit einem sehr ausgefeilten Vervollständigungs-System (programmierbare Wort-Vervollständigung).
Die gezielte Bereitstellung von Bash- oder tcsh-Features in der Z Shell sollen den Nutzern dieser beiden anderen Shells den Umstieg auf die Z Shell erleichtern. Eine Hilfestellung dazu gibt das Buch From Bash to Z Shell. Ob sich ein Umstieg auf eine andere Shell letztlich lohnt, muss jeder Anwender für sich entscheiden. Für viele Nutzer reicht der Umfang der Bash (oder auch der tcsh) sicher ohne weiteres aus. Sie werden vermutlich nie deren volle Leistung ausreizen. Eine gewisse Beschränkung der Komplexität hat auch Vorteile, weil man den Überblick besser behalten kann und so weniger Überraschungen erlebt.
Während die Bash 3 eine recht umfangreiche Unicode-Unterstützung bietet und bei der tcsh sowie bei der Z Shell Version 4.3 der Kommandozeilen-Editor Unicode-fähig ist, fehlt die Unicode-Unterstützung in der Z Shell bis zur Version 4.2. Daher ist die interaktive Nutzung älterer Versionen der Z Shell in einer Unicode-Umgebung (die zunehmend häufiger anzutreffen ist) teilweise sehr lästig und ggf. auch fehleranfällig, da man manchmal bestimmte Zeichen der Kommandozeile auf dem Bildschirm nicht mehr sieht. Diese Diskrepanz zwischen Anzeige und internem Inhalt ergibt sich nach dem Löschen von Mehrbyte-Zeichen, die zwar auf dem Bildschirm entfernt werden, im internen Kommandozeilen-Puffer aber teilweise erhalten bleiben, da nur ein Byte gelöscht wird.
Abschließend noch einige Bemerkungen zur Begriffs-Verwendung sowie zu typographischen Konventionen: Die Original-Begriffe der Shell-Programmierung wie auch allgemein der Informatik stammen aus dem Englischen und werden von alten "Shell-Hasen" oft auch im Original gebraucht. Im vorliegenden Text wurde vielfach geeigneten deutschen Entsprechungen der Vorzug gegeben. Solche allgemein bekannten Begriffe wie File für Datei und String für Zeichenkette werden dagegen verwendet.
Manche Termini lassen sich aber nur schwer übersetzen, z.B. das Substantiv Quoting oder das Verb quote, weil die wörtliche Übersetzung zitieren ungebräuchlich ist. Jeder Shell-Kenner weiß, dass es hier darum geht, die spezielle Bedeutung der vielen Sonderzeichen der Shell aufzuheben und statt dessen das betreffende Zeichen in seiner ursprünglichen Bedeutung (also als das Zeichen selbst) zu verwenden. Wir nutzen nachfolgend die eingedeutschten Begriffe quotieren bzw. Quotierung, die zumindest an das englische Original erinnern, sich aber in deutsche Satzkonstruktionen besser einfügen als z.B. die Konstruktion gequotet.
Beim Begriff History verzichten wir auf eine deutsche Übersetzung und verwenden statt dessen den englischen Ausdruck, weil er unter Shell-Kennern sofort klar und sehr gebräuchlich ist.
An verschiedenen Stellen des Textes wird die Syntax bestimmer Konstrukte erläutert und durch Beispiele illustriert. Dies erfolgt generell in einem Font fester Breite (Schreibmaschinenschrift). Die literal einzugebenden Zeichen bzw. Zeichenfolgen sind in Fettschrift angegeben, die symbolischen Platzhalter für Argumente u.ä. dagegen nicht.
In Syntax-Beschreibungen werden folgende Meta-Symbole genutzt:
Eckige Klammern schließen optionale Teile ein.
Beispiel:[Argumente]
Drei aufeinanderfolgende Punkte deuten eine Fortsetzung des zuvor angegebenen Elements an.
Beispiel:Wert ...
Ein senkrechter Strich trennt Alternativen.
Beispiel:-L|-P
Ein Umstieg von der tcsh zur Bash ist aus Sicht des Autors unter Beachtung seiner eigenen praktischen Erfahrungen relativ problemlos möglich. Durch die sehr zahlreichen Konfigurationsmöglichkeiten kann man die Bash in vielen Punkten so einstellen, dass sie sich (weitgehend oder genau so) wie die tcsh verhält. An einigen Stellen muss man allerdings umlernen, da bestimmte Features nicht oder in anderer Form angeboten werden. Als Entschädigung für diesen Aufwand erhält man aber auch neue Möglichkeiten, die in der tcsh fehlen. Dazu kommt, dass die meisten Skripte ohnehin Bourne-Shell-Skripte oder sogar direkt Bash-Skripte sind. Wenn man also die Bash statt der tcsh auch für die interaktive Arbeit nutzt, hat man eine einheitliche Umgebung für die interaktive Arbeit und die Skript-Programmierung.
Denjenigen tcsh-Fans, die mit einer interaktiven Bash dennoch nicht gut leben können oder wollen, sei ein Blick auf die zsh empfohlen, die ja die Welten der Bourne- und C-Shell zu vereinigen versucht. Es gibt Berichte verschiedener Nutzer, die erfolgreich von der Bash oder der tcsh zur Z Shell gewechselt sind und sich in der neuen Umgebung sehr wohl fühlen. Natürlich muss man auch beim Wechsel zur Z Shell einige kleine Änderungen in Kauf nehmen, z.B. die Tatsache, dass die Anzeige mehrerer Auswahlmöglichkeiten bei der Wort-Vervollständigung unter und nicht wie gewohnt über der aktuellen Eingabezeile erfolgt.
Wer mit dem keineswegs geringen Leistungsumfang der tcsh in der Regel auskommt, weil er interaktiv meist nur oder gerade die Dinge benötigt, die die tcsh gut beherrscht, und für komplexere Aufgaben auf Skript-Dateien statt interaktiv eingegebener Skripte zurückgreift, kann natürlich auch problemlos die tcsh als Standard-Shell behalten und sich in den relativ seltenen Fällen, in denen komplexere Aufgaben anliegen, eine Bash oder Z Shell manuell starten. Der Vorteil dieser auch vom Autor längere Zeit praktizierten Arbeistweise besteht darin, dass man für die meisten seiner Aufgaben eine Umgebung nutzt, die man besonders gut kennt und in der man daher auch sehr effizient arbeiten kann. Bei diesem Vorgehen empfiehlt es sich, die genutzten Shells so weit wie möglich identisch zu konfigurieren (z.B. identische Aliase, Variablen-Einstellungen, Optionen etc.), um einen nahtlosen Shell-Wechsel zu unterstützen.
Hier nun die (keineswegs vollständige) Liste von Unterschieden zwischen Bash und tcsh, die dem Autor aufgefallen sind:
Die allgemeine spelling correction (Tippfehler-Korrektur) steht in der Bash nicht zur Verfügung. Sie gibt es aber bei der Z Shell. Die Bash kennt lediglich die Option cdspell, die kleinere Tippfehler in Pfadkomponenten beim Kommando cd automatisch korrigiert und dann ohne Rückfrage das korrigierte Kommando ausführt.
Bei mehrzeiligen zusammengesetzten Kommandos übernimmt die tcsh generell nur die erste Zeile in die History, z.B. die foreach-Zeile einer entsprechenden Schleife.
Z Shell und Bash übernehmen dagegen die komplette mehrzeilige Konstruktion und bieten sie beim Zugriff auf die History je nach Option einzeilig oder auch mehrzeilig im Kommandozeilen-Editor an. Die diesbezüglichen Details der Bash finden sich im Abschnitt Kommandozeilen-Editor und History.
Die History-Einträge können ab Bash 3 wie in der tcsh auch mit Zeitstempeln versehen ausgegeben werden. Bis Bash 2 war dies nicht möglich.
Der tcsh-Operator >!, der ein Überschreiben von Dateien bei der Ausgabe-Umlenkung trotz aktiver Option noclobber bewirkt, ist bei der Bash durch >| zu ersetzen.
Eine Entsprechung für den tcsh-Operator >&! (gemeinsame Umlenkung von stdout und stderr mit Überschreiben von Dateien trotz aktiver Option noclobber) gibt es in der Bash nicht. Statt dessen kann man>|datei 2>&1notieren.
Der in einer Pipeline nutzbare Operator |&, der sowohl die Standard-Ausgabe als auch den Standard-Fehlerstrom eines Kommandos mit der Standard-Eingabe des Folge-Kommandos über eine Pipe verbindet, existiert bei der Bash erst ab Version 4. Außerdem gibt es ihn bei der Z Shell.
Bash und Z Shell bieten deutlich mächtigere Möglichkeiten der Deskriptor-Manipulation als die tcsh, z.B. die getrennte Umlenkung von stdout und stderr, die bei der tcsh nur über eine Hilfskonstruktion der Form
(kommando > /tmp/stdout) >& /tmp/stderr
machbar ist, die eine Sub-Shell verwendet.
Bash und tcsh bieten eine andere Syntax der Kontroll-Strukturen. Auf diesem Gebiet unterscheiden sich die Bourne- und C-Shell-Familie deutlich. Daher ist z.B. die foreach-Schleife bei der Bash nicht vorhanden. Sie ist durch die for-Schleife zu ersetzen, die aber eine andere Syntax hat.
Da die Z Shell beide Welten zu verbinden versucht, bietet sie z.B. foreach und for an.
Auch die Signal-Behandlung der Shell-Familien unterscheidet sich stark voneinander, was schon an den unterschiedlichen Kommandos deutlich wird, mit denen man die Reaktion auf ein Signal festlegen kann: trap bei der Bourne-Shell, onintr bei der C-Shell.
Aliase der Bash unterstützen keine Parameter. Dafür bietet die Bash im Gegensatz zur tcsh Shell-Funktionen an, die deutlich flexibler und leistungsfähiger als Aliase sind.
Um ein Kommando in einer modifizierten Umgebung auszuführen, benötigt man bei der tcsh das Kommando env. Bei der Bash ist es nicht nötig. Hier kann man z.B.
LC_ALL=C rm /statt
env LC_ALL=C rm /
verwenden.
Durch die stärkere Unicode-Unterstützung bei Bash 3 (die bis zur Z Shell Version 4.2 komplett fehlt und sich bei der Z Shell 4.3 sowie bei der tcsh auf den Kommandozeilen-Editor beschränkt) muss man Zeichenbereiche in Mustern für die Pfadnamens-Expandierung ggf. anders formulieren. Wer z.B. in einer UTF-8-Umgebung wie de_DE.UTF-8 arbeitet, muss [[:lower:]] statt [a-z] notieren, um wirklich nur die Kleinbuchstaben in die Menge aufzunehmen.
Eine zeitaufwendige Dateinamens-Vervollständigung bei sehr vielen Dateien kann man in der tcsh durch das (in der Regel durch Ctrl-C generierbare) Signal SIGINT bequem abbrechen, weil die zu vervollständigende Kommandozeile im Kommandozeilen-Editor erhalten bleibt und sofort weiter bearbeitet werden kann. Bei der Bash bricht SIGINT die aktuelle Zeile dagegen komplett ab, so dass man sie auch in der History nicht findet.
Bei der Vervollständigung von Datei- bzw. Pfadnamen, die mit der Tilde beginnen, erhalten tcsh und Z Shell die Tilde. Das Verhalten der Bash hängt vom Wert der Readline-Variablen expand-tilde ab. Hat sie den Wert off, wird die Tilde ebenfalls erhalten. Beim Wert on ersetzt die Bash die Tilde durch den absoluten Pfad des Home-Verzeichnisses desjenigen Nutzers, der durch den Tilde-Ausdruck benannt wird. Dadurch sind die Vervollständigungs-Ergebnisse länger und manchmal etwas unhandlicher.
Die Pfadnamens-Vervollständigung funktioniert bei der Bash nur, wenn der Cursor am Ende eines Wortes steht. Die tcsh vervollständigt generell ab Cursor-Position. Bei Variablennamen erfolgt die Vervollständigung bei Bash und tcsh mitten im Wort.
Bei der Z-Shell hängt das Verhalten generell von der Option COMPLETE_IN_WORD ab. Ist sie gesetzt, kann man jene Pfad-, Variablen- und andere Namen (Wörter) komplettieren, die mit dem links vom Cursor stehenden Wort-Teil beginnen und mit dem sich ab der Cursor-Position nach rechts erstreckenden Wort-Teil enden. Man kann sich also gezielt fehlende Teil-Strings am Anfang oder in der Mitte eines Namens ergänzen lassen.
Ist die Option COMPLETE_IN_WORD nicht gesetzt, so ergänzt die Z-Shell immer dasjenige Wort nach rechts, in dem bzw. hinter dem sich der Cursor befindet. Man kann sich also fehlende Wort-Enden ergänzen lassen, wobei die Cursor-Position nur für die Wort-Auswahl interessant ist. Die konkrete Zeichenposition im Wort ist dagegen irrelevant.
Sowohl tcsh als auch Bash kennen das eingebaute Kommando complete, mit dem man die Vervollständigung von Wörtern der Kommandozeile steuern kann (programmierbare Wort-Vervollständigung). Die konkrete Anwendung dieses Kommandos unterscheidet sich bei beiden Shells aber deutlich. Auch der Leistungsumfang bzw. die Flexibilität differieren.
Bei der tcsh wird die Vervollständigung nur über Optionen von complete gesteuert. Bash und Z Shell unterstützen dagegen die Nutzung von Shell-Funktionen zur Ermittlung der möglichen Vervollständigungen, woraus eine prinzipiell höhere Flexibilität resultiert.
Der bei der tcsh verfügbare Vervollständigungs-Modus enhance (einstellbar mit set complete=enhance) existiert nach Kenntnis des Autors in der Bash nicht, wenngleich man ihn über eine geeignete Vervollständigungs-Funktion nachbilden könnte. Dieser Modus bewirkt, dass bei der Vervollständigung
Wenn man also beispielsweise ein Muster der Art
p-.
vervollständigen lässt, findet man alle Pfadnamen, die mit p oder P beginnen und in denen irgendwo ein Bindestrich und später ein Punkt folgt, z.B.
phpMyAdmin-2.6.2-pl1.tar.bz2 Portal_Auth-Konzept_V3.doc Portal Auth-Konzept V4.sxw.bz2
Das Kommando stop zum Stoppen (Suspendieren) von Jobs steht in der Bash nicht zur Verfügung. Hier muss man explizit kill -STOP verwenden.
Typischerweise wird die Bash in einem Terminal-Emulator genutzt, der unter einer grafischen Oberfläche läuft. Das weit verbreitete Oberflächen-System KDE bietet z.B. als Terminal-Emulator das Kommando konsole an. Es gestattet, in einem Fenster mehrere Shell-Sitzungen parallel zu betreiben, so dass man mit einem einzigen Shell-Fenster auskommen kann und nicht für jede Shell ein eigenes Fenster benötigt, was die Übersichtlichkeit des Desktops erhöht.
Linux unterstützt mehrere virtuelle Terminals. Mit der Tastenkombination
Strg-Alt-Fn
kann man auf das virtuelle Terminal n umschalten.
Das virtuelle Terminal 7 ist meist das erste (und typischerweise das einzige) grafische Terminal. Die virtuellen Terminals 1 bis 6 bieten in der Regel eine Text-Konsole an. Dort wird nach einem erfolgreichen Anmelde-Vorgang eine Shell gestartet. Sie sind also nur über eine Shell vernünftig nutzbar.
Die Arbeit mit einer Text-Konsole kann sinnvoll oder nötig sein, wenn entweder das grafische Terminal (noch) nicht funktioniert oder durch einen anderen Nutzer gesperrt ist und man z.B. nur mal schnell seine Mails lesen oder eine Text-Datei editieren möchte. Dann kann man beispielsweise mit Strg-Alt-F1 auf die Text-Konsole 1 umschalten und dort die gewünschten Arbeiten erledigen.
Die allgemeine Aufruf-Syntax der Bash lautet:
bash [Optionen] [Skript-Datei]
Hinweis: In der Regel kann man die Bash auch unter dem Namen sh aufrufen. Dadurch verändert sich allerdings ihr Verhalten in einigen Punkten, primär bei der Art der Auswertung von Konfigurationsdateien. Darauf wird im Abschnitt Konfigurationsdateien genauer eingegangen.
Die Bash kann (wie andere Shells auch)
interaktiv, also im Dialog mit dem Benutzer, sowie
nicht-interaktiv, d.h. zur Ausführung von Shell-Skripten
genutzt werden.
Sie bietet eine Vielzahl von Optionen zur Beeinflussung ihres Verhaltens, so dass man sie sehr flexibel konfigurieren und gut an seine persönlichen Bedürfnisse anpassen kann.
Die meisten Optionen lassen sich innerhalb einer laufenden Shell jederzeit durch die eingebauten Kommandos set und shopt, die wir im Abschnitt Modifikation von Shell-Optionen in einer laufenden Shell genauer besprechen, abfragen und ändern. Zur Abfrage von Optionen können auch die nur lesbaren Variablen $- und SHELLOPTS genutzt werden. Eine existierende Umgebungsvariable SHELLOPTS wird beim Bash-Start ausgewertet, so dass man der Shell auch darüber Optionen übergeben kann.
Zusätzlich zu allen einbuchstabigen Optionen von set (inkl. -o bzw. +o, z.B. -o posix) stehen beim Aufruf der Bash die folgenden zusätzlichen Optionen zur Verfügung:
-c Kommando-String | Ausführung der Kommandos des Kommando-Strings |
-i | Start einer interaktiven Shell |
-l | Start einer Login-Shell |
-r | Start der Shell im eingeschränkten Modus |
-s | Shell-Kommandos von der Standard-Eingabe lesen
Dies gestattet das Setzen von Positionsparametern einer interaktiven Shell. |
-D | Ausgabe der Liste aller Strings, die der Locale-spezifischen
String-Übersetzung unterliegen, wenn die Locale nicht C oder
POSIX lautet, wobei die Strings in Doppelapostrophe
eingeschlossen werden und ihnen ein $ vorausgeht
Diese Option impliziert die Option -n , weswegen hier keine Kommandos ausgeführt werden. Beispiel: Das Kommando bash -D -c 'echo $"stdout" stderr $"stdin"' erzeugt die Ausgabe "stdout" "stdin" |
-O[shopt-Option] +O[shopt-Option] |
Setzen oder Rücksetzen von shopt-Optionen
Mit -O wird die betreffende Option gesetzt, mit +O zurückgesetzt. Wenn die Angabe der shopt-Option fehlt, dann gibt die Bash die Liste aller verfügbaren shopt-Optionen zusammen mit ihrem Vorzugs-Wert auf die Standard-Ausgabe aus. Bei +O erfolgt diese Ausgabe in Form von shopt-Kommandos, mit denen man die Vorzugs-Werte einstellen könnte. |
- -- |
Diese funktionell äquivalenten Optionen (ein bzw. zwei Minus-Zeichen) kennzeichnen das Ende der Optionen-Liste. Dahinter folgende Argumente werden dann nicht mehr als Optionen gewertet, auch wenn sie mit einem Minus-Zeichen beginnen. |
Auf die folgenden beiden einbuchstabigen set-Optionen sei explizit hingewiesen, da sie für das Debugging von Skripten nützlich sein können:
-v | Anzeige der Kommandozeilen, wie sie eingelesen wurden
Kurzform von verbose |
-x | Anzeige der Kommandozeilen, wie sie ausgeführt werden (nach allen
Expandierungen bzw. Substitutionen)
Kurzform von xtrace |
Die Bash akzeptiert auch eine Reihe mehrbuchstabiger Aufruf-Optionen. Diese müssen vor evtl. vorhandenen einbuchstabigen Optionen angegeben werden:
--debugger | bei (gepatchter) Bash 3.0 Aufruf des Bash-Debuggers von Rocky Bernstein, dessen Interface dem des GNU-Debuggers GDB ähnelt |
--dump-strings | äquivalent zur Option -D |
--dump-po-strings | äquivalent zur Option -D, wobei die Ausgabe im PO-Format
(Portable Object Format) von gettext erfolgt
Beispiel: Das zum oben bei Option -D verwendeten Kommando analoge Kommando bash --dump-po-strings -c 'echo $"stdout" stderr $"stdin"' erzeugt hier die Ausgabe #: -c:0 msgid "stdout" msgstr "" #: -c:0 msgid "stdin" msgstr "" |
--help | kurzen Hilfetext ausgeben, der den Aufruf der Bash erläutert |
--init-file Datei --rcfile Datei |
Verwendung der angegebenen Datei an Stelle der Datei ~/.bashrc als persönliche Initialisierungs- bzw. Konfigurations-Datei einer interaktiven Shell |
--login | äquivalent zur Option -l |
--noediting | Readline-Bibliothek (flexibler Kommandozeilen-Editor) in einer interaktiven Shell nicht verwenden |
--noprofile | systemweite oder persönliche Konfigurations-Dateien nicht einlesen, die normalerweise von einer Login-Shell ausgewertet werden |
--norc | die persönliche Konfigurations-Datei ~/.bashrc in einer
interaktiven Shell nicht auswerten
Diese Option wird automatisch gesetzt, wenn man die Bash unter dem Namen sh aufruft. |
--posix | die Bash im POSIX-Modus starten |
--restricted | äquivalent zur Option -r |
--verbose | äquivalent zur Option -v |
--version | Versions-Information der Bash ausgeben und Shell dann sofort beenden |
Eine interaktive Shell (Bash) ist eine Shell, die
entweder ohne Nicht-Options-Argumente und ohne die Option -c (zur Angabe eines Kommando-Strings) gestartet wurde und deren Standard-Eingabe sowie -Fehlerstrom mit einem Terminal (ermittelt über die Bibliotheksfunktion isatty) verbunden ist
oder mit der Option -i aufgerufen wurde.
Hinweis: Man kann die Positionsparameter einer interaktiven Shell setzen, wenn man sie mit der Option -s aufruft, die erzwingt, dass die Shell ihre Kommandos von der Standard-Eingabe liest. Die inhaltlich identischen zwei Kommandos des folgenden Beispiels starten jeweils eine interaktive Shell, deren erste drei Positionsparameter auf die Werte 1, 2 und 3 gesetzt sind:
bash -s 1 2 3 bash -i -s 1 2 3
Die Variable PS1 für den primären Prompt (Bereitschaftszeichen der Shell) ist bei interaktiven Shells im Gegensatz zu nicht-interaktiven Shells gesetzt. Der Wert der Variablen $-, die die aktuell gesetzten einbuchstabigen Shell-Optionen anzeigt, enthält den Buchstaben i, um zu signalisieren, dass es sich um eine interaktive Shell handelt.
Man kann also in einem Skript und damit auch in einer Konfigurationsdatei wie ~/.bashrc feststellen, ob es sich um eine interaktive Shell handelt oder nicht. Hier ein Beispiel:
case "$-" in *i*) echo interaktiv ;; *) echo nicht interaktiv ;; esac if [[ -z $PS1 ]] then echo nicht interaktiv else echo interaktiv fi
Eine interaktive Shell kann konkret folgendermaßen gestartet werden:
implizit als typische Login-Shell laut /etc/passwd
Anmerkung: Eine Login-Shell ist eine Shell, bei der entweder das erste Zeichen von Argument 0 ein - ist (z.B. -sh) oder die mit der Option -l bzw. --login gestartet wurde:
bash -l bash --login
Eine mit der Option -l bzw. --login aktivierte Login-Shell kann auch nicht-interaktiv sein, z.B. so:
echo date | bash -l > datum
# Eine nicht-interaktive Login-Shell bekommt mittels Pipe
# über ihre Standard-Eingabe den String date übergeben, den sie als
# Kommando ausführt. Dessen Ausgabe (das aktuelle Datum und die aktuelle
# Uhrzeit) wird durch eine Ausgabe-Umlenkung in die Datei datum
# geschrieben.
Hinweis: Speziell unter Linux kann man normalerweise die Bash auch unter dem Namen sh statt bash aufrufen. Dies hat u.a. eine andere Art der Auswertung von Konfigurationsdateien zur Folge. Auch die Tatsache, dass eine Shell als Login-Shell gestartet wird, hat Einfluss auf die Auswertung von Konfigurationsdateien.
bash [Optionen]
bash -i [weitere Optionen]
Interaktive Shells zeigen durch einen Prompt die Bereitschaft zur Entgegennahme einer Kommandozeile an.
Die vier verfügbaren Bash-Prompts sind konfigurierbar, indem man den folgenden Prompt-Variablen geeignete Prompt-Strings zuweist, die dann in den entsprechenden Situationen als Prompts ausgegeben werden:
Prompt-Variable | Bedeutung |
---|---|
PS1 | Primär-Prompt |
PS2 | Sekundär-Prompt (ab Zeile 2 bei mehrzeiligen Kommandos) |
PS3 | Prompt für Menüs beim Kommando select |
PS4 | Prompt für den Trace-Modus (Option -x) |
Primär- und Sekundär-Prompt (PS1 und PS2) sind sehr flexibel anpassbar. Über speziell dafür vorgesehene Escape-Sequenzen kann man viele Informationen in den Prompts unterbringen. Ein Beispiel hierzu findet sich im Abschnitt Flexible Prompt-Anpassung.
Kommandos können meist schon "auf Vorrat" (also vor Erscheinen des nächsten Prompts) eingetippt werden. Sie werden in der Regel zwischengespeichert und später ausgeführt, wenn die Shell neue Kommandos akzeptieren kann.
Auch für den Start einer nicht-interaktiven Bash gibt es mehrere Möglichkeiten:
bash [Optionen] Skript-Datei [Argumente] bash -c Kommando-String [Argumente] Kommandos_von_stdin | bash
Das Argument Skript-Datei ist der Name einer Datei, deren Inhalt als Shell-Skript, also eine Folge von Shell-Kommandos betrachtet und von der Shell entsprechend interpretiert wird. Der Kommando-String ist eine Zeichenfolge, die die Shell ebenfalls als Folge von Shell-Kommandos interpretiert.
Hinter der Skript-Datei angegebene Argumente werden der Reihe nach als Positionsparameter $1 ... gesetzt. Argumente, die dem Kommando-String folgen, werden der Reihe nach den Parametern $0 (spezieller Parameter) sowie $1 ... (Positionsparameter) zugewiesen.
Hinweis: Die erste Zeile der Skript-Datei sollte keine Nicht-ASCII-Zeichen (z.B. Umlaute und ß) enthalten, da sonst die Bash die Abarbeitung mit der Bemerkung
cannot execute binary file
verweigert.
Wie oben schon erwähnt, kann man speziell unter Linux die Bash normalerweise auch unter dem Namen sh statt bash aufrufen, wodurch sich u.a. die Auswertung von Konfigurationsdateien ändert.
Beispiel-Skript hello_world.sh:
#!/bin/sh # der Shell-Variablen text den Wert Hello World zuweisen text='Hello World' # den Inhalt der Variablen text auf die Standard-Ausgabe ausgeben echo "$text"
Aufruf-Beispiele der Bash:
# Skript hello_world.sh starten bash hello_world.sh # Skript hello_world.sh starten; # alle Zeilen anzeigen, wie sie eingelesen wurden (Option -v) und wie # sie (nach allen Expandierungen/Substitutionen) ausgeführt werden # (Option -x) bash -vx hello_world.sh # Ausgabe der Summe 3+4 bash -c 'echo $((3+4))' # Kommando finger über die Standard-Eingabe übergeben und von # der Bash ausführen lassen echo finger | bash # Skript hello_world.sh unter Steuerung des Bash-Debuggers ausführen bash --debugger hello_world.sh
Anmerkung zur Interpreter-Zeile (Zeile 1) von Skripten:
Unix-Systeme sind in der Lage, Interpreter-Dateien auszuführen. Das sind Dateien, die mit einer Zeile folgender Form beginnen:
#! absoluter_Pfad_zum_Interpreter [Optionen]Beim Start einer ausführbaren Interpreter-Datei wird der angegebene Interpreter (z.B. die Shell, AWK, Perl oder Python) aufgerufen. Ihm übergibt der Unix-Kern drei Argumente:
- Argument 1: absoluter Pfad zum Interpreter aus der Interpreter-Zeile
- Argument 2: alle Optionen der Interpreter-Zeile als ein String
- Argument 3: Dateiname der Interpreter-Datei in der Form, wie er in der Shell zum Aufruf der Interpreter-Datei angegeben wurde
Beispiele für Interpreter-Zeilen:
#!/bin/sh #!/bin/bash #!/bin/sh -x #!/bin/sh -Eine Interpreter-Zeile der Form
#!/bin/sh -x -vführt zu einer Fehlermeldung der Shell, da sie die Option -x -v nicht kennt. Man kann hier also keine Liste einzelner Optionen nutzen, weil der Kern alle Zeichen, die zum Optionsfeld der Interpreter-Zeile gehören, geschlossen als ein Argument an den Interpreter weitergibt. Allerdings wäre im konkreten Beispiel die Angabe
#!/bin/sh -xvmit dem gewünschten Effekt nutzbar.
Durch die Interpreter-Zeile der Form
#!/bin/sh -kann man verhindern, dass der Shell eine ungewollte Option übergeben wird, wenn man die Interpreter-Datei über einen Dateinamen aufruft, der einer Option gleicht.
Würde man beispielsweise ein Skript, dessen Interpreter-Zeile
#!/bin/shlautet, über den Dateinamen -i (harter oder symbolischer Link auf die Skript-Datei) rufen, würde der Shell die Option -i übergeben werden, wodurch man eine interaktive Shell erhielte, das Skript in der Datei aber nicht ausgeführt würde.
Konkret könnte das so passieren:
ln -s pfad_zum_skript /tmp/-i PATH=/tmp -iWenn es sich bei der Interpreter-Datei um ein Setuid-/Setgid-Programm handelt, könnte dies ggf. dazu führen, dass ein unberechtigter Nutzer eine interaktive Shell starten kann, die nicht mit seinen Rechten, sondern mit den Rechten des Eigentümers der Interpreter-Datei läuft (im Extremfall mit Root-Rechten).
Unter Linux und anderen modernen Unix-Systemen besteht allerdings keine derartige Gefahr, da Setuid-/Setgid-Skripten vom Kern aus Sicherheitsgründen nicht zugelassen werden. Außerdem ist zu erwähnen, dass die Bash nur dann eine Abweichung von realer und effektiver UID (User ID) bzw. GID (Group ID) gestattet, wenn sie im privilegierten Modus (privileged mode) läuft, was sie nur dann tut, wenn explizit die Option p bzw. privileged mit dem Kommando set oder durch die Aufruf-Option -p aktiviert wurde.
Da die Bash standardmäßig nicht im privilegierten Modus läuft, setzt sie nach dem Start die effektive auf die reale UID bzw. GID zurück, sofern diese Werte differieren. Wenn man in einer laufenden Bash den privilegierten Modus verlässt, indem man die Option p deaktiviert (set +p), wird die effektive UID bzw. GID auf den jeweils realen Wert zurückgesetzt.
Falls beim Bash-Start die reale und effektive UID und/oder GID differieren, werden unabhängig davon, ob der privilegierte Modus aktiviert ist, aus Sicherheitsgründen die Umgebungsvariablen ENV und BASH_ENV (und damit Konfigurationsdateien) nicht ausgewertet und Shell-Funktionen nicht aus der Umgebung vererbt. Außerdem wird die Umgebungvariable SHELLOPTS ignoriert.
Eine laufende Bash kann durch das Kommando
exit
beendet werden.
Eine Login-Shell kann man zusätzlich mit dem Kommando
logout
beenden. In einer Nicht-Login-Shell führt die Nutzung von logout dagegen zu einer Fehlermeldung:
bash: logout: not login shell: use `exit'
Eine nicht-interaktive Shell terminiert automatisch immer dann, wenn das ihr zur Ausführung übergebene Skript beendet ist, wenn also die Shell beim Versuch, das nächste Kommando einzulesen, ein EOF (Dateiende-Signal, End Of File) bekommt.
Auch eine interaktive Shell kann man mittels EOF beenden. Dazu muss man mit einer geeigneten Tastenkombination (die meist Ctrl-D lautet, aber mit dem Kommando stty auch geändert werden kann) das EOF-Zeichen als erstes Zeichen einer Kommandozeile eingeben, ggf. mehrmals hintereinander. Der Wert der Shell-Variablen IGNOREEOF gibt dabei an, wie viele EOF-Zeichen hintereinander einzugeben sind, bis die Bash beendet wird. Wenn diese Variable einen nicht-numerischen Wert oder den leeren String als Wert hat, wird sie implizit mit dem Wert 10 angenommen. Falls IGNOREEOF nicht existiert, genügt ein einziges EOF-Zeichen zur Terminierung der Shell.
Außerdem kennt die Bash noch die Option ignoreeof. Sie kann man mit dem Kommando
set -o ignoreeof
setzen. Dies entspricht der Zuweisung der Zahl 10 an die Variable IGNOREEOF:
IGNOREEOF=10
Die Option ignoreeof wird von der Bash automatisch immer als gesetzt betrachtet, wenn die Variable IGNOREEOF existiert. Man kann diese Option also auch durch eine Zuweisung an IGNOREEOF setzen. Um sie zurückzusetzen, hat man folglich zwei Möglichkeiten:
set +o ignoreeof
unset IGNOREEOF
# löscht die Variable IGNOREEOF
Eine in einem Terminal-Emulator unter einer grafischen Oberfläche ausgeführte Shell lässt sich auch durch Beendigung des Terminal-Emulators terminieren.
Von der Art des Aufrufs der Bash hängt ab, welche Konfigurationsdateien sie in welcher Reihenfolge auswertet. Nachfolgend soll eine Auswahl von typischen Konstellationen beschrieben werden.
Wird die Bash nicht über das Kommando bash, sondern sh aufgerufen, verhält sie sich bzgl. der Konfigurationsdateien sowie des Bash-Modus anders.
Beim Aufruf der Bash über das Kommando bash gilt:
Interaktive und nicht-interaktive Login-Shells werten standardmäßig folgende Konfigurationsdateien in der angegebenen Reihenfolge aus:
/etc/profile, sofern sie lesbar ist
die erste lesbare Datei der folgenden geordneten Liste:
Anmerkung: Die Tilde (~) am Anfang von Dateinamen symbolisiert den absoluten Pfad zum Home-Verzeichnis des Nutzers, der die Bash gestartet hat. Die Tilde wird im Rahmen der Tilde-Expandierung durch den betreffenden Pfad ersetzt. In manchen Dokumentationen wird statt der Tilde der Ausdruck $HOME verwendet, weil typischerweise die Shell-Variable HOME ebenfalls den absoluten Pfad zum Home-Verzeichnis des Nutzers enthält.
Mit der Option --noprofile kann das Auswerten der o.g. Dateien verhindert werden.
Beim Beenden einer Login-Shell wird die Datei ~/.bash_logout ausgewertet, sofern sie lesbar ist.
Eine interaktive Shell, die keine Login-Shell ist, wertet standardmäßig die Datei ~/.bashrc aus.
Die Auswertung von ~/.bashrc lässt sich durch die Option --norc unterdrücken.
Mit der Option --rcfile kann eine Datei spezifiziert werden, die an Stelle von ~/.bashrc ausgewertet wird.
Eine nicht-interaktive Bash wertet diejenige Konfigurationsdatei aus, deren Name in der Umgebungsvariablen BASH_ENV gespeichert ist. Wenn BASH_ENV leer ist, wird keine Konfigurationsdatei gelesen. Mit Shell-Kommandos lässt sich dieses Verhalten so ausdrücken:
if [[ -n $BASH_ENV ]] then source "$BASH_ENV" fi
Anmerkung: Wenn es sich bei der nicht-interaktiven Bash um eine Login-Shell handelt, dann wird BASH_ENV erst nach den o.g. Konfigurationsdateien für Login-Shells gelesen.
Beim Aufruf der Bash über das Kommando sh gilt:
Interaktive und nicht-interaktive Login-Shells werten standardmäßig folgende Konfigurationsdateien aus, sofern sie lesbar sind:
Mit der Option --noprofile kann das Auswerten der o.g. Dateien verhindert werden.
Eine interaktive Shell wertet diejenige Konfigurationsdatei aus, deren Name in der Umgebungsvariablen ENV gespeichert ist. Wenn ENV leer ist, wird keine Konfigurationsdatei gelesen. Mit Shell-Kommandos lässt sich diese Verhalten so ausdrücken:
if [[ -n $ENV ]] then source "$ENV" fi
Die Option --rcfile zur Angabe einer alternativen Konfigurationsdatei ist wirkungslos, da maximal die Datei gelesen wird, deren Name in ENV steht.
Eine nicht-interaktive Shell, die keine Login-Shell ist, wertet überhaupt keine Konfigurationsdateien aus.
Eine über sh aufgerufene Bash wechselt nach dem Lesen der Konfigurationsdateien in den POSIX-Modus.
Um also Shell-Skripten ohne Beeinflussung von Konfigurationsdateien auszuführen, sollte man die Bash über das Kommando sh rufen:
sh skript.sh
Als Interpreter-Zeile von Shell-Skripten empfiehlt sich daher eine der folgenden Formen:
#! /bin/sh #! /bin/sh -
Eine im POSIX-Modus gestartete Bash folgt dem POSIX-Standard für Konfigurationsdateien. Interaktive Shells expandieren dort die Variable ENV, werten das Ergebnis als Dateiname und führen die Kommandos aus, die in dieser Datei stehen. Andere Konfigurationsdateien werden generell nicht gelesen.
Eine interaktive Shell nutzt standardmäßig die GNU-Bibliothek Readline als Benutzerschnittstelle. Sie bietet einen komfortablen und sehr flexibel konfigurierbaren Kommandozeilen-Editor sowie einen Zugriff auf eine History-Liste (Liste früherer Kommandos). Durch die Bash-Option --noediting kann die Verwendung der Readline-Bibliothek unterbunden werden.
Zur Initialisierung der Readline-Bibliothek dient die Datei, deren Name in der Umgebungsvariablen INPUTRC steht. Sie wird beim Start einer Bash eingelesen. Wenn die Variable INPUTRC nicht gesetzt ist, versucht die Shell, der Reihe nach die beiden Dateien /etc/inputrc und ~/.inputrc zu lesen. Erläuterungen dazu folgen weiter unten.
Hinweis: Das Syntax-Highlighting des weit verbreiteten Editors Vim schaltet standardmäßig nur dann in den Bash-Modus um, wenn in Zeile 1 des Skripts
#! /bin/bash
steht. Um auch bei Bash-Skripten, die mit
#! /bin/sh
beginnen, den Bash-Modus des Highlightings nutzen zu können, muss man der Vim-Variablen is_bash den Wert 1 zuweisen. Diese Zuweisung sollte in der Datei ~/.vimrc erfolgen:
let is_bash=1
Shell-Optionen werden mit den Kommandos set und shopt angezeigt und modifiziert. Mit dem von der klassischen Bourne-Shell stammenden set kann man nur auf einen Teil der Optionen, mit dem Bash-spezifischen shopt dagegen auf sämtliche Optionen zugreifen. Eine komplette Beschreibung der Bash-Optionen findet man im Online- oder Referenz-Manual. Dabei empfiehlt es sich vor allem, die Abschnitte zu den eingebauten Kommandos set und shopt sowie zur Aufruf-Syntax der Bash zu konsultieren.
Fast alle mit set veränderbaren Optionen haben neben einer Langform auch noch eine einbuchstabige Kurzform, beispielsweise xtrace und x. Daher sind die folgenden beiden Anweisungen funktionell identisch:
set -o xtrace set -x
Das Minus-Zeichen bei -o und -x bewirkt das Setzen (Aktivieren) der betreffenden Option (im Beispiel also xtrace bzw. x). Um eine solche Option wieder zurückzusetzen bzw. zu deaktivieren, verwendet man statt des Minus-Zeichens ein Plus-Zeichen:
set +o xtrace set +x
Man kann mit einem set-Kommando auch mehrere Optionen setzen und rücksetzen, wobei sich Kurz- und Langform mischen lassen, z.B. so:
set +o xtrace -m -o pipefail
# xtrace zurücksetzen und m sowie pipefail setzen
Hinweis: Mit set kann man auch die Positionsparameter einer Shell-Instanz oder einer Shell-Funktion setzen und löschen sowie alle Shell-Variablen mit ihren aktuellen Werten ausgeben lassen. Das wird im Abschnitt Variablen, Parameter und Felder beschrieben.
Das Kommando shopt kennt die Option -s zum Setzen und -u zum Rücksetzen einer Option. Hier zwei Beispiele:
shopt -s dotglob shopt -u dotglob
Statt einer einzelnen Option kann man auch eine Liste angeben:
shopt -s dotglob nullglob extglob shopt -u promptvars shift_verbose
Ein gleichzeitiges Setzen und Rücksetzen von Optionen ist bei shopt aber nicht möglich.
Um mit shopt auch die Optionen von set zu manipulieren, muss man zusätzlich die Option -o verwenden, wie das folgende Beispiel zeigt:
shopt -u -o xtrace pipefail shopt -s -o xtrace pipefail oder shopt -uo xtrace pipefail shopt -so xtrace pipefail
Als Argumente sind hier die langen set-Optionen anzugeben.
Die aktuellen Werte der set-Optionen kann man sich durch die Kommandos
set -o shopt -o set +o
anzeigen lassen. Sie unterscheiden sich lediglich hinsichtlich des Ausgabe-Formats. Die ersten beiden geben Zeilen der Form
allexport off braceexpand on
aus, die vorn die Option und dahinter deren aktuellen Wert enthalten.
Das dritte Kommando generiert set-Kommandos, mit denen man die aktuelle Einstellung wiederherstellen könnte, z.B.:
set +o allexport set -o braceexpand
Die Anzeige der ausschließlich mit shopt einstellbaren Optionen erfolgt so:
shopt
Hier erhält man Zeilen der Form
cdable_vars off cdspell on
Wenn dabei hinter shopt noch Options-Namen als Argumente folgen, werden nur die Einstellungen der genannten Optionen ausgegeben:
shopt failglob gnu_errfmt huponexit
# Beispiel-Ausgabe:
# failglob off
# gnu_errfmt off
# huponexit off
Mit den beiden Kommandos
shopt -s shopt -u
kann man sich die gesetzten bzw. nicht gesetzten shopt-Optionen anzeigen lassen. Für die set-Optionen funktioniert dies analog so:
shopt -s -o shopt -u -o oder shopt -so shopt -uo
Bei all diesen Anzeige-Kommandos bewirkt die Option -p, dass die Anzeige in Form von Kommandos erfolgt, mit denen man die aktuellen Einstellungen wiederherstellen könnte:
shopt -p # Format der Ausgabe: # shopt -u shift_verbose # shopt -s sourcepath # shopt -u xpg_echo shopt -po # Format der Ausgabe: # set +o verbose # set +o vi # set +o xtrace
Die shopt-Option -q bewirkt, dass keine Ausgaben erfolgen, sondern das Ergebnis der Abfrage über den Exit-Status mitgeteilt wird. Er lautet 0, wenn alle abgefragten Optionen gesetzt sind, ansonsten 1.
Beispiele:
shopt -q lithist sourcepath shopt -qo physical histexpand
Die Liste der aktuell aktiven einbuchstabigen Optionen kann man auch der Shell-Variablen $- entnehmen. Wenn also z.B. die Option x gesetzt ist, kommt der Buchstabe x im Wert von $- vor. Ein Beispiel-Wert könnte himxBCH lauten. Die analoge Liste der langen set-Optionen ist der Variablen SHELLOPTS zu entnehmen, die z.B. den Wert
braceexpand:emacs:hashall:histexpand:interactive-comments:monitor:noclobber:physical
haben könnte.
Eine existierende Umgebungsvariable SHELLOPTS wird beim Start der Bash ausgewertet, sofern die Shell nicht im privilegierten Modus läuft. Somit kann man über sie von außen Optionen setzen. In einer laufenden Bash ist SHELLOPTS aber nur lesbar. Eine Wertzuweisung an diese Variable ist daher nicht möglich. Um aus einer Bash eine andere Bash mit einem modifizierten Wert von SHELLOPTS zu starten, kann man das Kommando env verwenden, wie folgendes Beispiel zeigt:
env SHELLOPTS=xtrace bash
Beispiele zur Anzeige und Modifikation von Optionen, die die wichtigsten Punkte nochmal zusammenfassen:
set -x # Option x bzw. xtrace aktivieren shopt -so xtrace # dito mit shopt set +x # Option x bzw. xtrace deaktivieren shopt -uo xtrace # dito mit shopt # für die set-Option vi gibt es keine Kurzform: set -o vi # Option vi und damit den Vi-Modus des Kommandozeilen-Editors # aktivieren (der Emacs-Modus wird damit automatisch deaktiviert) set +o vi # Option vi und damit den Vi-Modus deaktivieren (der Emacs-Modus # wird dabei nicht automatisch aktiviert) # Die analogen beiden shopt-Kommandos sehen so aus: shopt -so vi shopt -uo vi set -o # Anzeige der aktuellen Einstellung aller set-Optionen shopt -o # dito mit shopt set +o # dito, aber in Form von set-Kommandos shopt -po # dito mit shopt shopt -s dotglob # Option dotglob aktivieren shopt -u dotglob # Option dotglob deaktivieren shopt # Anzeige der aktuellen Einstellung aller shopt-Optionen shopt -s # Anzeige aller gesetzten shopt-Optionen shopt -u # Anzeige aller zurückgesetzten shopt-Optionen shopt -so # Anzeige aller gesetzten set-Optionen shopt -uo # Anzeige aller zurückgesetzten set-Optionen echo $SHELLOPTS # Anzeige der aktiven set-Optionen in der Langform echo $- # Anzeige der aktiven einbuchstabigen set-Optionen
Die Bash kann in verschiedenen Modi ausgeführt werden, auf die wir hier kurz hinweisen wollen. Standardmäßig läuft die Bash im nativen Modus, den wir im vorliegenden Text bei der Erläuterung von Sachverhalten allgemein unterstellen und nicht gesondert erwähnen.
Weitere Modi sind:
Der mit der Option p bzw. privileged aktivierbare privilegierte Modus wurde bereits beim Start einer nicht-interaktiven Bash beschrieben.
Der eingeschränkte Modus wird aktiviert, wenn man die Bash mit der Option -r bzw. --restricted oder unter dem Namen rbash aufruft. In diesem Modus stehen aus Sicherheitsgründen bestimmte Möglichkeiten der Bash nicht zur Verfügung, z.B. der Verzeichnis-Wechsel mit cd. Damit eignet sich der eingeschränkte Modus z.B. recht gut für die Realisierung von Gast-Accounts. Details zu den Einschränkungen findet man im Online-Manual unter RESTRICTED SHELL.
Der POSIX-Modus wird aktiviert, wenn beim Aufruf der Bash die Option --posix angegeben wurde oder die Umgebungsvariable POSIXLY_CORRECT existierte. In einer laufenden Shell kann man den POSIX-Modus durch
set -o posix
einschalten. Auch der Aufruf der Bash unter dem Namen sh hat den POSIX-Modus zur Folge, da er hierbei nach dem Einlesen der Konfigurationsdateien automatisch aktiviert wird.
Anmerkung: Man kann die Bash bei der Übersetzung aus den Quellen so konfigurieren, dass sie standardmäßig im POSIX-Modus läuft. Davon ist aber abzuraten.
Im POSIX-Modus orientiert sich die Bash strenger am Standard POSIX 1003.2 und ändert daher an verschiedenen Stellen ihr Verhalten gegenüber dem nativen Modus. Diese Änderungen sind im Referenz-Handbuch, nicht aber im Online-Manual beschrieben und werden in der folgenden Liste aufgeführt, wobei wir nicht alle genannten Punkte im restlichen Text diskutieren werden:
Die Bash verhält sich generell so, also ob die Option checkhash gesetzt wäre. Beim Aufruf eines Kommandos, dessen Hash-Tabellen-Eintrag veraltet ist, wird daher automatisch eine neue Suche des Kommandos im Such-Pfad (PATH) veranlasst.
Die Nachricht, die die Bash ausgibt, wenn ein Hintergrund-Job mit einem Exit-Status ungleich 0 (also fehlerhaft) beendet wird, lautet
Done(Exit-Status)
In runden Klammern wird also der Exit-Status angegeben.
Die Nachricht, die die Bash ausgibt, wenn ein Job suspendiert wurde, lautet
Stopped(Signal-Name)
In runden Klammern wird also der Signal-Name des Stopp-Signals angegeben, z.B. SIGTSTP.
Die vom eingebauten Kommando bg ausgegebene Beschreibung der in den Hintergrund verschobenen Jobs enthält keine Information darüber, ob der Job der aktuelle oder der vorher aktuelle Job ist.
Reservierte Wörter, die in einem Kontext auftauchen, in dem reservierte Wörter erkannt werden, unterliegen keiner Alias-Expandierung.
Unabhängig von der Option promptvars wird in den Werten der Prompt-Variablen PS1 und PS2 das Zeichen ! zur History-Nummer und die Zeichenfolge !! zu ! expandiert. Im Gegensatz zu den sonstigen Spezialzeichen des Bash-Prompts geht diesen beiden hier kein Backslash voran.
Statt der normalen Bash-Konfigurationsdateien wird nur die Datei gelesen, deren Name in der Shell-Variablen ENV steht.
Die Tilde-Expandierung erfolgt nicht in allen Zuweisungen einer Zeile, sondern nur in denjenigen, die einem Kommando-Namen vorausgehen.
Der Vorzugswert der Variablen HISTFILE lautet ~/.sh_history. Das ist die Default-History-Datei.
kill -l gibt alle Signal-Namen getrennt durch Leerzeichen auf einer einzigen Zeile aus, wobei das Präfix SIG weggelassen wird.
Das Builtin-Kommando kill akzeptiert keine Signal-Namen mit dem SIG-Präfix.
Nicht-interaktive Shells werden beendet, wenn eine mit dem Kommando source bzw. . einzulesende Datei nicht gefunden wird.
Nicht-interaktive Shells werden beendet, wenn ein auszuwertender arithmetischer Ausdruck einen Syntaxfehler aufweist.
Umlenkungs-Operatoren realisieren in nicht-interaktiven Shells keine Pfadnamens-Expandierung in dem dahinter folgenden Wort.
Umlenkungs-Operatoren realisieren in dem dahinter folgenden Wort keine Aufspaltung in Wörter (Word Splitting).
Funktionsnamen müssen gültige Shell-Bezeichner sein. Sie dürfen also nur Buchstaben, Ziffern und Unterstriche enthalten und dürfen nicht mit einer Ziffer beginnen. Die Verwendung ungültiger Namen bei einer Funktionsdefinition führt in nicht-interaktiven Shells zu einem fatalen Fehler, also zum Beenden der Shell.
Die POSIX-Spezial-Builtin-Kommandos (POSIX special builtins), die man sich mit enable -s anzeigen lassen kann, werden vor gleichnamigen Shell-Funktionen ausgeführt. Zu diesen Spezial-Builtin-Kommandos gehören folgende:
. : break continue eval exec exit export readonly return set shift source trap unset
Wenn die Spezial-Builtin-Kommandos einen Fehler-Status liefern, wird eine nicht-interaktive Shell beendet. Zu den fatalen Fehlern gehören beispielsweise die Angabe inkorrekter Optionen, Umlenkungs-Fehler und Fehler bei Variablen-Zuweisungen, die einem Kommando vorausgehen.
Wenn cd sein Zielverzeichnis über die Shell-Variable CDPATH findet, dann enthält der dabei an die Variable PWD zugewiesene Wert keine symbolischen Links. Implizit wird also cd -P ausgeführt.
Wenn die Shell-Variable CDPATH gesetzt ist, wird beim Kommando cd nicht automatisch das aktuelle Verzeichnis implizit angehängt. Somit schlägt cd fehl, wenn das als Argument angegebene Ziel-Verzeichnis zwar im aktuellen, aber in keinem der explizit in CDPATH genannten Verzeichnisse vorkommt.
Eine nicht-interaktive Shell wird mit einem Fehler-Status beendet, wenn hinter einer fehlerhaften Variablen-Zuweisung (z.B. einer Zuweisung an eine nur lesbare Variable) kein Kommando folgt.
Eine nicht-interaktive Shell wird mit einem Fehler-Status beendet, wenn die Iterations-Variable einer for-Schleife oder die Auswahl-Variable einer select-Anweisung nur lesbar ist.
Die Prozess-Substitution ist nicht verfügbar.
Variablen-Zuweisungen, die vor POSIX-Spezial-Builtin-Kommandos stehen, verbleiben dauerhaft in der Umgebung, sind also auch nach der Ausführung des betreffenden Builtin-Kommandos gültig.
Variablen-Zuweisungen, die vor dem Aufruf einer Shell-Funktion stehen, verbleiben dauerhaft in der Umgebung, wenn innerhalb der Funktion ein POSIX-Spezial-Builtin-Kommando ausgeführt wurde.
Die Builtin-Kommandos export und readonly zeigen ihre Ausgaben in dem von POSIX 1003.2 geforderten Format an.
Das Builtin-Kommando trap zeigt Signal-Namen ohne das Präfix SIG an.
Das Builtin-Kommando trap untersucht nicht, ob es sich bei seinem ersten Argument um eine Signal-Spezifikation handelt, was im nativen Modus zur Folge hätte, dass für alle genannten Signale die Original-Einstellung reaktiviert würde, also diejenige, die beim Start der Bash aktiv war. Im POSIX-Modus kann man die Original-Einstellung von Signalen reaktivieren, wenn man als erstes Argument eine gültige Signal-Nummer oder ein Minus-Zeichen angibt, wobei sich letzteres empfiehlt.
Die Builtin-Kommandos . und source suchen nicht im aktuellen Verzeichnis nach der einzulesenden Datei, nachdem diese über den Such-Pfad PATH nicht gefunden wurde.
Sub-Shells, die zum Zwecke der Kommando-Substitution gestartet werden, erben den Wert der Option -e von ihrer Eltern-Shell. Im nativen Modus deaktiviert die Bash diese Option generell für derartige Sub-Shells.
Alias-Expandierungen sind standardmäßig immer verfügbar, auch in nicht-interaktiven Shells.
Das eingebaute Kommando alias zeigt die Liste der Alias-Definitionen ohne das Präfix alias an, es sei denn, die Option -p wurde angegeben.
Wenn das eingebaute Kommando set ohne Optionen aufgerufen wird, zeigt es keine Namen und Definitionen von Shell-Funktionen an.
Wenn das eingebaute Kommando set ohne Optionen aufgerufen wird, zeigt es Variablenwerte ohne umschließende Quotierungs-Zeichen an, es sei denn, sie enthalten Shell-Metazeichen. D.h., das Auftreten nicht-druckbarer Zeichen im Variablenwert zieht keine Quotierung nach sich.
Wenn das eingebaute Kommando cd im logischen Modus (Option -L) aufgerufen wird und der aus dem logischen Pfad zum aktuellen Verzeichnis sowie dem als Argument angegebenen Verzeichnis generierte Pfad nicht existiert, dann schlägt cd fehl, statt in den physischen Modus (Option -P) zurückzufallen.
Ein Beispiel hierzu findet sich bei der Beschreibung des Kommandos cd.
Folgende Features von POSIX 1003.2 sind in der Bash nicht implementiert:
Zuweisungen beeinflussen die Ausführungs-Umgebung aller Builtin-Kommandos und nicht nur die Umgebung der POSIX-Spezial-Builtin-Kommandos.
Wenn die Bash eine Sub-Shell kreiert, um ein Shell-Skript auszuführen, das in einer (laut Zugriffsrechten) ausführbaren Datei ohne Interpreter-Zeile (#!) gespeichert ist, dann übergibt sie an die Variable $0 des Skripts den durch eine PATH-Suche ermittelten absoluten Pfadnamen des Skripts und nicht das ggf. abweichende Kommando, das der Nutzer angegeben hat.
Wenn die Bash ein im Suchpfad PATH gefundenes externes Skript mit dem Kommando . bzw. source einliest, dann prüft sie in den Zugriffsrechten die Ausführbarkeit und nicht die Lesbarkeit des Skripts, als ob sie eine Kommando-Suche ausführen würde.
Im Online-Manual der Bash wird folgende Terminologie verwendet:
Leerraum (blank) | Leerzeichen oder Tabulator |
Wort oder Token (word, token) | Zeichenfolge, die von der Shell als einzelne Einheit betrachtet wird |
Name oder Bezeichner (name, identifier) | Wort, das nur aus alphanumerischen Zeichen und Unterstrichen (underscores) besteht und mit einem Buchstaben oder Unterstrich beginnt |
Metazeichen (metacharacter) | Zeichen mit (nicht durch Quotierung aufgehobener) Sonderbedeutung, das als
Worttrenner wirkt:
| & ; ( ) < > Leerzeichen TabulatorAnmerkung: In der Literatur wird der Begriff Metazeichen oft weiter gefasst. Darunter versteht man häufig alle Zeichen, die in der Shell standardmäßig eine Sonderbedeutung haben und somit nicht für sich selbst stehen. Wir werden nachfolgend meist auch diese allgemeinere Bedeutung unterstellen. |
Operator (control operator) | Token mit Steuerfunktion:
|| & && ; ;; ( ) | Newline |
Reservierte Wörter (reserved words) | Wörter mit (nicht durch Quotierung aufgehobener) Sonderbedeutung für die Shell, wenn sie
als erstes Wort eines einfachen Kommandos oder als drittes Wort eines
case- oder for-Kommandos stehen:
! case do done elif else esac fi for function if in select then until while { } time [[ ]] |
Anmerkung: Die Variable IFS (Internal Field Separator) wird bei der Bash nur an zwei Stellen im Rahmen des Word Splitting (Aufteilung eines Strings in Wörter) verwendet:
nach Expandierungen/Substitutionen bei der Kommandozeilen-Auswertung,
beim Kommando read zum Zerlegen des eingelesenen Strings.
Beim Aufspalten von Kommandozeilen in einzelne Token spielt IFS dagegen keine Rolle. Hier werden nur die o.g. Metazeichen als Trenner verwendet.
Beispiele zur Nutung von IFS:
# alten Wert von IFS merken ORIG_IFS=$IFS # IFS auf den Doppelpunkt setzen IFS=: # über den Datei-Deskriptor 3 die Datei /etc/passwd zum Lesen öffnen exec 3< /etc/passwd # in einer while-Schleife die Zeilen der Datei vom Deskriptor 3 einlesen, # die einzelnen Wörter einer Zeile einem Feld (Array) zuordnen und über eine # for-Schleife die Feld-Elemente (Wörter der Zeile) auf separaten Zeilen # ausgeben while read -a felder -u 3 do for i in "${felder[@]}" do echo "$i" done echo '=======' done # IFS auf den alten Wert setzen, um z.B. Listen normal expandieren zu lassen IFS=$ORIG_IFS ZAHLEN="1 2 3 4 5 6" for i in $ZAHLEN do echo "$i" done
Komplexeres Beispiel: SCRIPTS/passwd_ausgabe.sh
Bei der Besprechung der Aufspaltung in Wörter wird die Verwendung von IFS noch detaillierter diskutiert.
Ein mit einem Doppelkreuz beginnendes Wort leitet einen Kommentar ein:
# Ich bin ein Kommentar. echo 123 # Ausgabe von 123
Wenn die (standardmäßig eingeschaltete) Option interactive_comments aktiv ist, kann man Kommentare auch in interaktiven Shells verwenden. Mit den folgenden beiden Kommandos lässt sich diese Option ein- bzw. ausschalten:
shopt -s interactive_comments shopt -u interactive_comments
Die Bash-Grammatik kennt sechs zentrale Elemente:
Koprozesse (ab Bash 4)
Syntaktischer Aufbau:
optionale Variablenzuweisungen (Setzen von Umgebungsvariablen für genau dieses Kommando)
gefolgt von durch Leerräume getrennten Wörtern sowie Ein-/Ausgabe-Umlenkungen und
beendet durch einen Operator.
Das erste Wort spezifiziert das auszuführende Kommando. Die Folgewörter sind Optionen/Argumente, die an das Kommando weitergegeben werden.
Mit dem Kommando type kann man den Typ eines Kommandos ermitteln. Fünf Typen sind möglich:
eingebaut (builtin), also ein internes Kommando der Shell
extern, also ein externes Programm im Dateisystem, das von der Shell aufgerufen wird
Alias, also der Name eines über das Kommando alias definierten Alias-Konstrukts, der als Abkürzung oder alternativer Bezeichner für ein anderes Shell-Konstrukt dient
Alle Kommandos liefern einen Rückkehrwert. Er entspricht dem Exit-Status des Kommandos oder 128+n, falls das Kommando durch Signal n beendet wurde.
Typischerweise signalisiert der Exit-Status 0 den "Erfolg" (also die erfolgreiche bzw. fehlerfreie Ausführung eines Kommandos). Andere Werte weisen in der Regel auf einen Fehler hin.
Der Exit-Status lässt sich abfragen und zur Programmsteuerung nutzen. Den Exit-Status eines Shell-Skripts kann man durch das Kommando exit gezielt setzen.
Bei gleichnamigen Konstrukten gilt folgende Ausführungs-Reihenfolge:
Alias
reserviertes Wort
Shell-Funktion
eingebautes Kommando
externes Kommando
Anmerkung: Es ist zulässig, auch reservierte Wörter als Alias-Namen oder Namen von Shell-Funktionen zu verwenden.
Es gibt verschiedene Möglichkeiten zur Beeinflussung der Ausführungs-Reihenfolge:
Durch Quotierung (z.B. Voranstellen eines Backslashs) kann man die Alias-Expandierung und die Erkennung reservierter Wörter verhindern.
Durch das eingebaute Kommando builtin kann man die Ausführung eines Builtin-Kommandos veranlassen.
Durch das Builtin-Kommando command kann man erreichen, dass ein Name als Name eines eingebauten oder externen Kommandos interpretiert wird, wobei eingebaute Kommandos Vorrang vor externen haben.
Durch Angabe seines absoluten Pfades kann man explizit ein externes Kommando aufrufen.
Durch das Builtin-Kommando enable kann man eingebaute Kommandos aktivieren bzw. deaktivieren.
Beispiele für einfache Kommandos:
id # UID/GIDs des aktuellen Nutzers anzeigen pwd # aktuelles Verzeichnis anzeigen cd /etc # Wechsel des aktuellen Verzeichnisses nach /etc type cd # Typ des Kommandos cd ermitteln # mit Muster für Datei-/Pfadnamen und Ausgabe-Umlenkung: # # - ho* bezeichnet Dateien, deren Name mit ho beginnt # - [a-gp]* bezeichnet Dateien, deren Name mit einem der Buchstaben # a bis g oder p beginnt # - [!a]*.? bezeichnet Dateien, deren Name nicht mit dem Buchstaben # a beginnt und dessen vorletztes Zeichen ein Punkt ist # - [^a]*.? ist eine andere Notation für [!a]*.? # - > /tmp/erg.txt lenkt die Standard-Ausgabe in die Datei /tmp/erg.txt um, # wobei eine existierende Datei überschrieben wird, sofern die Option # noclobber nicht aktiv ist # - >> /tmp/erg.txt lenkt die Standard-Ausgabe in die Datei /tmp/erg.txt um, # wobei eine existierende Datei fortgeschrieben wird # # Achtung: # Welche Zeichen zu einem Zeichenbereich, z.B. [a-g], gehören, hängt # von der sog. Locale (Lokalisierung, Anpassung der landessprachlichen # Einstellungen) ab, in der die Bash bzw. ein Bash-Kommando ausgeführt wird. # # So umfasst z.B. der Zeichenbereich [a-z] in der Locale C nur # genau alle Kleinbuchstaben. In der Locale de_DE.UTF-8 gehören neben # den Kleinbuchstaben auch noch die korrespondierenden Großbuchstaben sowie # Umlaute und andere von den Grundbuchstaben durch diakritische Zeichen # abgeleitete Zeichen dazu. # # Mit Hilfe der POSIX-Zeichenklassen kann man Locale-unabhängige # Zeichenbereiche bilden. Z.B. umfasst die POSIX-Zeichenklasse lower # alle Kleinbuchstaben. # # Nutzungsbeispiel: # echo [[:lower:]]* # # Weitere Informationen dazu finden sich im Abschnitt # Metazeichen für Pfadnamens-Muster. # ls -ld ho* [a-gp]* > /tmp/erg.txt echo [!a]*.? >> /tmp/erg.txt echo [^a]*.? >> /tmp/erg.txt # Datei /tmp/erg.txt anzeigen cat /tmp/erg.txt # Wurzel des Dateibaums löschen (funktioniert nicht; erzeugt eine Fehlermeldung) rm / # Umgebungsvariable LC_ALL auf C setzen # (die Locale C bewirkt englische Fehlermeldungen) LC_ALL=C rm / # $? hält den Exit-Status des letzten Vordergrund-Kommandos echo $? # Exit-Status 1 setzen und Skript beenden exit 1 # Beispiele für gleichnamige Konstrukte # Aliase pwd und if definieren alias pwd='echo Alias pwd' alias if='echo Alias if' # Shell-Funktionen pwd und if definieren function pwd() { echo Funktion pwd } function if() { echo Funktion if } # Aufrufe von pwd pwd # Alias-Expandierung zu echo Alias pwd \pwd # Funktions-Aufruf; Ausführung von echo Funktion pwd builtin pwd # Aufruf des eingebauten Kommandos pwd command pwd # Aufruf des (eingebauten) Kommandos pwd /bin/pwd # Aufruf des externen Kommandos /bin/pwd enable -n pwd # eingebautes Kommando pwd deaktivieren pwd # externes Kommando pwd rufen, z.B. /bin/pwd (laut Such-Pfad) enable pwd # eingebautes Kommando pwd wieder aktivieren # Aufrufe von if if # Alias-Expandierung zu echo Alias if \if # Funktions-Aufruf; Ausführung von echo Funktion if # Durch die Quotierung wird if nicht als reserviertes Wort # betrachtet. unalias if # den Alias if löschen if # if wirkt nun als reserviertes Wort und leitet # eine (vollständige oder unvollständige) Alternative ein # Hinweis zu type: type if # Ausgabe: if is a shell keyword # Da der Alias if gelöscht wurde, wird if korrekt als # reserviertes Wort gewertet. Allerdings kann man mit type # nicht erkennen, dass man mit \if eine Funktion aufruft: type \if # Ausgabe wie oben: if is a shell keyword # # Dieses Verhalten ist logisch: # An das eingebaute Kommando type wird das Argument if übergeben. # Der Backslash als Quotierungs-Zeichen wird ganz normal entfernt. # # Wenn wir allerdings den Alias if wieder definieren, meldet type, # dass es sich um einen Alias handelt: alias if='echo Alias if' type if # Ausgabe: if is aliased to `echo Alias if'
Der Such-Pfad ist eine Liste von durch je einen Doppelpunkt voneinander getrennten Verzeichnissen, die nach externen Kommandos zu durchsuchen sind:
# Suchpfad anzeigen echo "$PATH" # /tmp als erstes Element in den Suchpfad aufnehmen PATH="/tmp:$PATH"
Die Bash verwaltet eine Hash-Tabelle, in der sie sich automatisch alle bereits aufgerufenen externen Kommandos zusammen mit deren absolutem Pfad merkt. Wenn ein externes Kommando aufgerufen werden soll, prüft die Bash zuerst, ob dieses Kommando in der Hash-Tabelle zu finden ist. Falls ja, verwendet sie den dort gespeicherten absoluten Pfad. Anderenfalls durchsucht sie die in PATH genannten Verzeichnisse nach dem Kommando. Wurde es gefunden, ergänzt die Bash diesen Treffer in der Hash-Tabelle.
Wenn die shopt-Option checkhash aktiv ist, prüft die Bash, ob ein in der Hash-Tabelle gefundenes Kommando existiert, bevor sie versucht, es auszuführen. Wenn es nicht mehr existiert, wird der veraltete Eintrag gelöscht und eine normale PATH-Suche ausgeführt. Bei deaktivierter Option checkhash wird ein veralteter Pfad aus der Hash-Tabelle ohne Prüfung verwendet, was dann natürlich zu einem Fehler führt.
Die Hash-Tabelle kann mit dem eingebauten Kommando hash angezeigt und modifiziert werden.
Standardmäßig startet die Shell die Kommandos mit drei offenen Dateien (Kanälen):
Kanalbezeichnung | Kanalnummer bzw. Datei- oder File-Deskriptor |
---|---|
Standard-Eingabe (stdin) | 0 |
Standard-Ausgabe (stdout) | 1 |
Standard-Fehler (stderr) | 2 |
Diese Kanäle sind im Standardfall mit dem Terminal verbunden. Mittels der Ein-/Ausgabe-Umlenkung kann man sie mit einer anderen Quelle oder Senke verbinden: reguläre Datei, Named Pipe (benannte Pipe, FIFO), Gerätedatei (z.B. /dev/zero oder /dev/null), ...
Durch explizite Angabe können auch die Datei-Deskriptoren >= 3 in der Shell genutzt werden. Generell sind nur natürliche Zahlen als Datei-Deskriptor zulässig.
Umlenkungen können sich auf einzelne Kommandos, Kommando-Gruppen sowie die aktuelle Shell im Ganzen beziehen. Letzteres legt die Standard-Umlenkungen für alle nachfolgend ausgeführten Kommandos fest.
Umlenkungen können an beliebigen Stellen innerhalb eines einfachen Kommandos stehen oder einem Kommando folgen. Sie werden von links nach rechts ausgewertet. Ihre Reihenfolge ist daher teilweise wichtig:
# stdout/stderr gemeinsam nach file umlenken ls > file 2>&1 # nur stdout nach file umlenken; # stderr wird zuvor auf die vorherige Einstellung von stdout umgelenkt ls 2>&1 > file
Umlenkungs-Operatoren bestehen aus einem oder zwei Zeichen. Operatoren für Eingabe-Umlenkungen beginnen mit dem Zeichen < und Operatoren für Ausgabe-Umlenkungen mit dem Zeichen >. Wenn in den folgenden Beschreibungen die Angabe des Datei-Deskriptors fehlt, beziehen sich Eingabe-Umlenkungen immer auf die Standard-Eingabe (Deskriptor 0) und Ausgabe-Umlenkungen immer auf die Standard-Ausgabe (Deskriptor 1).
Das Wort, das dem Umlenkungs-Operator folgt, unterliegt standardmäßig einer Reihe von Expandierungen: Expandierung geschweifter Klammern, Tilde-Expandierung, Parameter-, Kommando- und arithmetische Substitutionen, Aufspaltung in Wörter, Pfadnamens-Expandierung sowie Entfernung der Quotierungs-Zeichen. Wenn im Ergebnis mehr als ein Wort entsteht, meldet die Bash einen Fehler.
Die Bash kennt bei den Umlenkungen verschiedene Spezialdateien:
/dev/fd/fd # Deskriptor fd /dev/stdin # Deskriptor 0 /dev/stdout # Deskriptor 1 /dev/stderr # Deskriptor 2 /dev/tcp/host/port # TCP-Verbindung zu Host/Port /dev/udp/host/port # UDP-Verbindung zu Host/Port
Nachfolgend werden die verschiedenen Umlenkungen mit den dafür vorgesehenen Umlenkungs-Operatoren (Metazeichen) aufgelistet, wobei die Operatoren zur Hervorhebung rot geschrieben sind:
Eingabe-Umlenkung:
Format:
[n]<WortDie Datei, deren Name sich durch die Expandierungen von Wort ergibt, wird zum Lesen geöffnet und mit dem Deskriptor n (Standard: 0) verbunden.
Ausgabe-Umlenkung:
Format:
[n]>WortDie Datei, deren Name sich durch die Expandierungen von Wort ergibt, wird zum Schreiben geöffnet und mit dem Deskriptor n (Standard: 1) verbunden. Falls die Datei noch nicht existiert, wird sie erzeugt. Andernfalls wird sie auf die Länge Null gekürzt (der alte Inhalt geht also verloren).
Wenn die set-Option noclobber gesetzt ist, scheitert die Ausgabe-Umlenkung, wenn die Ausgabe-Datei bereits existiert.
Eine existierende Datei kann (sofern man die nötigen Rechte dafür besitzt) durch eine Ausgabe-Umlenkung überschrieben werden, wenn man die Option noclobber deaktiviert oder den Umlenkungs-Operator
[n]>|Wortverwendet.
Ausgabe-Umlenkung mit Anfügung:
Format:
[n]>>WortDie Datei, deren Name sich durch die Expandierungen von Wort ergibt, wird zum Schreiben im Anfüge-Modus geöffnet und mit dem Deskriptor n verbunden. Eine existierende Datei wird also fortgeschrieben. Eine noch nicht existente Datei wird (wie oben bei der Ausgabe-Umlenkung) erzeugt.
Gemeinsame Umlenkung von Standard-Ausgabe und -Fehlerstrom:
Formate:
&>Wort >&WortStandard-Ausgabe und -Fehlerstrom werden in die Datei umgelenkt, deren Name sich durch die Expandierungen von Wort ergibt. Beide genannten Formate sind funktionell identisch und äquivalent zu
>Wort 2>&1Der Operator &> sollte generell bevorzugt werden.
Bei gesetzter Option noclobber scheitert diese gemeinsame Umlenkung ebenso wie die oben beschriebene einfache Ausgabe-Umlenkung. Man kann sich allerdings auch hier über noclobber hinwegsetzen:
>|Wort 2>&1Hinweis: Der bei tcsh und zsh existierende Operator |& zur gemeinsamen Umlenkung von Standard-Ausgabe und -Fehlerstrom in eine Pipe steht bei der Bash erst ab Version 4 zur Verfügung. Bis Bash 3 muss der Standard-Fehlerstrom explizit vor dem Pipe-Zeichen umgelenkt werden:
# Nutzung |& bei tcsh und zsh diff -ru verzeichnis1 verzeichnis2 |& vim - # äquivalentes Kommando bei der Bash diff -ru verzeichnis1 verzeichnis2 2>&1 | vim -
Gemeinsame Umlenkung von Standard-Ausgabe und -Fehlerstrom mit Anfügung:
Format:
&>>WortStandard-Ausgabe und -Fehlerstrom werden an die Datei angefügt, deren Name sich durch die Expandierungen von Wort ergibt. Dieses Format ist semantisch äquivalent zu
>>Wort 2>&1
Hier-Dokumente:
Format:
<<[-]Wort Hier-Dokument BegrenzerDie Standard-Eingabe wird auf ein Hier-Dokument (here document), d.h. eine Zeilenfolge umgelenkt, die an der aktuellen Stelle eines Shell-Skripts (also "hier") angegeben ist. Dadurch kann man die im Rahmen einer Eingabe-Umlenkung zu lesenden Daten im aktuellen Shell-Skript unterbringen und so auf eine externe Datei verzichten.
Der Begrenzer entspricht dem Wort nach der Entfernung evtl. Quotierungs-Zeichen. Er steht in der Regel alleine auf einer Zeile. Hinter ihm folgt also sofort ein Newline-Zeichen. Leerräume hinter dem Begrenzer sind generell nicht zulässig. Dem Begrenzer dürfen Tabulatoren vorausgehen, sofern vor dem Wort ein Minus-Zeichen steht. Andere Zeichen vor dem Begrenzer sind unzulässig.
Ein - vor Wort bewirkt das Streichen von Tabulator-Zeichen (nicht aber Leerzeichen!!) am Anfang jeder Zeile des Hier-Dokuments einschließlich der Begrenzer-Zeile. Damit kann man ein Hier-Dokument im Shell-Skript geeignet einrücken, ohne seinen Inhalt zu verändern.
Das Wort selbst unterliegt keiner Parameter-, Kommando-, arithmetischen und Pfadnamens-Expandierung. Allerdings werden standardmäßig im Hier-Dokument Parameter-, Kommando- und arithmetische Expandierungen durchgeführt und die Zeichenfolge Backslash Newline ignoriert.
Die Expandierungen kann man selektiv unterbinden, indem man die Sonderbedeutung der Zeichen
\ $ `durch Voranstellen eines Backslashs aufhebt.
Wenn man (mindestens) ein Zeichen von Wort quotiert, werden Expandierungen innerhalb des Hier-Dokuments komplett unterbunden.
Hier-Strings:
Format:
<<<WortDas ist eine Variante der Hier-Dokumente. Das Wort wird expandiert und dann im Rahmen der Eingabe-Umlenkung als Eingabe verwendet. Dabei wird hinten an das Expansions-Ergebnis immer automatisch ein Newline-Zeichen (Zeilenschaltung) angehängt.
Duplizierung von Datei-Deskriptoren:
Formate:
[n]<&Wort [n]>&WortFormat 1:
Der Operator <& dupliziert Eingabe-Datei-Deskriptoren. Wenn im Ergebnis der Expandierung von Wort eine Ziffernfolge (also eine natürliche Zahl) entsteht, dann wird der Deskriptor n (Standard: 0) als Kopie des durch Zahl benannten Deskriptors eingerichtet. Beide Deskriptoren verweisen dann auf dieselbe Datei.
Falls die Zahl keinen zum Lesen geöffneten Deskriptor benennt, erfolgt eine Fehlermeldung.
Sofern sich bei der Expandierung von Wort ein Minus-Zeichen ergibt, dann wird der Deskriptor n (Standard: 0) komplett geschlossen, unabhängig davon, ob er zum Lesen und/oder Schreiben geöffnet war.
Format 2:
Der Operator >& dupliziert Ausgabe-Datei-Deskriptoren. Wenn im Ergebnis der Expandierung von Wort eine Ziffernfolge (also eine natürliche Zahl) entsteht, dann wird der Deskriptor n (Standard: 1) als Kopie des durch Zahl benannten Deskriptors eingerichtet. Beide Deskriptoren verweisen dann auf dieselbe Datei.
Wenn die Zahl keinen zum Schreiben geöffneten Deskriptor benennt, erfolgt eine Fehlermeldung.
Wenn sich bei der Expandierung von Wort ein Minus-Zeichen ergibt, dann wird der Deskriptor n (Standard: 1) komplett geschlossen, unabhängig davon, ob er zum Lesen und/oder Schreiben geöffnet war.
Wenn n fehlt und Wort weder zu einer Ziffernfolge noch zu einem Minus-Zeichen expandiert, dann liegt der obige Spezialfall der gemeinsamen Umlenkung von Standard-Ausgabe und -Fehlerstrom vor.
Verschieben von Datei-Deskriptoren:
Formate:
[n]<&Wort- [n]>&Wort-Wort muss hier zu genau einer natürlichen Zahl expandiert werden. Beide Formate verschieben (duplizieren) den Datei-Deskriptor Zahl nach Datei-Deskriptor n (Standard: 0 bei <& und 1 bei >&) und schließen danach Deskriptor Zahl.
Datei-Deskriptoren zum Lesen und Schreiben öffnen:
Format:
[n]<>WortDie Datei, deren Name sich durch die Expandierungen von Wort ergibt, wird zum Lesen und Schreiben geöffnet und mit dem Deskriptor n (Standard: 0) verbunden. Eine nicht existierende Datei wird erzeugt.
Hinweis zu Leerräumen bei Umlenkungs-Operatoren:
Das hinter einem Operator folgende Wort kann dem Operator unmittelbar folgen oder auch durch Leerräume von ihm getrennt sein. Die Wirkung unterscheidet sich dabei nicht. Daher sind die folgenden beiden Zeilen inhaltlich identisch:
ls > /tmp/ls.txt ls >/tmp/ls.txtZwischen einer optionalen Zahl n, die dem Operator vorausgeht, sowie dem Operator selbst darf kein Leerraum stehen. Zwischen den Zeichen eines aus 2 Zeichen bestehenden Operators (z.B. <>) darf ebenfalls kein Leerraum stehen. Das folgende Beispiel soll dies veranschaulichen:
# korrekte Umlenkung des Deskriptors 2 (stderr) auf Deskriptor 1 (stdout): exec 2>& 1 # falsch: exec 2 >& 1 exec 2 > & 1 exec 2> & 1Sofern einem Operator keine Zahl n vorausgeht, muss der Operator von einem vorausgehenden Wort nicht durch Leerräume getrennt werden. Die folgenden Zeilen sind deshalb wirkungsgleich:
ls>& /tmp/ls.txt ls >&/tmp/ls.txt ls >& /tmp/ls.txt
Beispiel zu Deskriptor-Manipulationen: Skript SCRIPTS/deskr.sh
#!/bin/sh # # Deskriptor-Manipulationen in der Bash # # 5.9.2008 # die folgenden beiden read-Kommandos lesen 2 Mal die erste (und damit # dieselbe) Zeile aus $file, da durch die getrennten Umlenkungen die Datei 2 # Mal separat geöffnet wird file=/etc/services read -r < $file ; echo "$REPLY" read -r < $file ; echo "$REPLY" echo '=====' # die ersten 10 Zeilen aus $file einlesen und mit vorangestellter Zeilennummer # ausgeben for ((i = 0; i < 10; i++)) { read -r ; echo "$i: $REPLY" } < $file echo '=====' # $file unter FD (File-Deskriptor) 3 zum Lesen öffnen exec 3< $file # die ersten beiden Zeilen von FD 3 lesen und ausgeben; für den Zugriff auf den # Deskriptor 3 nutzt Variante 1 die Eingabe-Umlenkung und Variante 2 die Option # -u von read read -r <&3 ; echo "$REPLY" read -r -u 3 ; echo "$REPLY" # FD 3 wieder schließen exec 3<&- echo '=====' # FD 0 nach FD 3 duplizieren und $file unter FD 0 öffnen exec 3<&0 < $file # die ersten beiden Zeilen von FD 0 lesen read -r ; echo "$REPLY" read -r ; echo "$REPLY" echo '=====' # FD 3 (Kopie des alten FD 0) nach FD 0 verschieben und FD 3 schließen exec <&3- # noch eine Zeile vom alten (beim Start des Skripts aktuellen) FD 0 lesen read -r ; echo "$REPLY"
Aufruf des Skripts:
echo xyz | bash deskr.sh
Beispiel zur bidirektionalen TCP-Kommunikation unter Verwendung des Operators <>: Skript SCRIPTS/get_http.sh
#!/bin/sh # # simples Beispiel, das eine Web-Seite via GET holt # # 6.4.2006 # Server-Adresse, Port, URL SERVER=localhost PORT=80 URL=/ # Deskriptor 3 mit dem HTTP-Server verbinden exec 3<>"/dev/tcp/$SERVER/$PORT" # GET-Anforderung senden echo "GET $URL" >& 3 # Antwort lesen und ausgeben while read -ru 3 do echo "$REPLY" done
Weitere Beispiele:
# Eingabe-Umlenkung (stdin) durch < # alle Terminal-Einstellungen von /dev/ttyS0 (serielle Schnittstelle 1 bzw. COM1) # anzeigen stty -a </dev/ttyS0 # Dateinamen aus der Datei file_list lesen und an ls -ld weitergeben; # die 3 folgenden Kommandos sind inhaltlich identisch, auch wenn die Eingabe-Umlenkung # an verschiedenen Stellen auftaucht xargs < file_list ls -ld < file_list xargs ls -ld xargs ls -ld < file_list # Einlesen des Version-Strings des SSH-Dämons an Port 22 auf localhost read -r ssh_vers < /dev/tcp/localhost/22 # Ausgabe-Umlenkung (stdout) durch > ls -l > /tmp/ls.out # Ausgabe-Umlenkung (stdout) durch >| # existierende Datei wird trotz Option noclobber überschrieben ls -l >| /tmp/ls.out # Ausgabe-Umlenkung (stdout) durch >> # falls die Datei existiert, wird die Ausgabe angehängt ls -l /etc >> /tmp/ls.out # stdout und stderr getrennt umlenken touch /tmp/z rm -fv /tmp/z / > /tmp/out 2> /tmp/err # stdout und stderr zusammen umlenken # Variante 1 (Reihenfolge der Umlenkungen ist wichtig!): rm -f / > /tmp/rm.out 2>&1 # Variante 2: rm -f / &> /tmp/rm.out # Variante 3 wie bei tcsh (Variante 2 sollte bevorzugt werden): rm -f / >& /tmp/rm.out # Hier-Dokument: Eingabe-Umlenkung durch << name=Otto # Variante 1 (ohne Löschung von Tabs am Zeilenanfang: # cat <<! # Variante 2 (mit Löschung von Tabs am Zeilenanfang): cat <<-! Hallo, ich bin ein Hier-Dokument mit Substitutionen $name $(date) ! # Hier dient das ! als Begrenzer. Da es nicht quotiert ist, # werden im Hier-Dokument Parameter-, Kommando- und arithmetische # Expandierungen vorgenommen. # Im folgenden Beispiel wird der Begrenzer quotiert. Dadurch # werden alle Expandierungen im Hier-Dokument unterbunden. cat <<-\! Hallo, ich bin ein Hier-Dokument ohne Substitutionen: $name $(date) ! # Hier-String: Eingabe-Umlenkung durch <<< cat <<< "Ich bin ein Hier-String: $name" # vier Newline-Zeichen via Standard-Eingabe an ein Kommando übergeben wc -c <<< $'\n\n\n'
Jedes Zeichen eines bei der Pfadnamens-Expandierung genutzten Pfadnamens-Musters, das nicht zu den unten genannten Spezial- bzw. Metazeichen gehört, steht für sich selbst. Wenn also beispielsweise ein Muster an einer bestimmten Stelle das Zeichen a enthält, dann passen nur jene Pfadnamen zu diesem Muster, die an der entsprechenden Stelle ein a haben (wobei bei aktiver Option nocaseglob auch der Großbuchstabe A passt).
Das NUL-Zeichen (Zeichen mit dem Wert 0) kann nie in einem Muster vorkommen. Ein Backslash quotiert das Folgezeichen eines Musters, hebt also dessen Sonderbedeutung auf. Die Sonder- bzw. Metazeichen müssen quotiert werden, wenn sie literal verwendet werden sollen.
Standardmäßig kennt die Bash folgende Metazeichen für Pfadnamens-Muster, die im Rahmen der Pfadnamens-Expandierung eine Sonderbedeutung haben:
* | beliebige (auch leere) Zeichenfolge |
? | ein beliebiges Zeichen |
[...] | Zeichenmenge |
[!...] | negierte Zeichenmenge |
[^...] | negierte Zeichenmenge (andere Notation für [!...]) |
Um eine Zeichenmenge anzugeben, kann man alle zur Menge gehörenden Zeichen einzeln aufzählen. Sofern die schließende eckige Klammer (]) auch zur Menge gehört, muss sie als erstes Zeichen angegeben werden, z.B. so: []abc].
Wenn unmittelbar hinter der öffnenden eckigen Klammer einer Zeichenmengen-Angabe das Zeichen ! oder ^ folgt, handelt es sich um eine negierte Menge. D.h., genau die zwischen den eckigen Klammern angegebenen Zeichen gehören nicht zur Menge, was zur Folge hat, dass alle nicht angegebenen Zeichen zur Menge gehören.
Zusätzlich sind in Zeichenmengen auch Bereichsangaben möglich. Ein solcher Bereich wird in Form eines durch einen Bindestrich getrennten Zeichenpaares notiert (z.B. [a-g]) und umfasst die beiden explizit aufgeführten Bereichsbegrenzungen sowie alle Zeichen, die laut Sortierung zwischen den beiden Bereichsgrenzen liegen.
Achtung: Welche Zeichen zu einem Zeichenbereich gehören, hängt von der sog. Locale (Lokalisierung, Anpassung der landessprachlichen Einstellungen) ab, in der die Bash bzw. ein Bash-Kommando ausgeführt wird. Relevant sind hier die folgenden drei Umgebungsvariablen, die in der genannten Reihenfolge konsultiert werden:
LC_COLLATE
LC_ALL
LANG
Die erste Variable, die gesetzt ist, wird verwendet.
So umfasst z.B. der Zeichenbereich [a-z] in der Locale C bzw. POSIX nur genau alle Kleinbuchstaben. In der Locale de_DE.UTF-8 gehören neben den Kleinbuchstaben auch noch die korrespondierenden Großbuchstaben sowie Umlaute und andere von den Grundbuchstaben durch diakritische Zeichen abgeleitete Zeichen dazu.
Mit Hilfe der nachfolgend dargestellten POSIX-Zeichenklassen kann man Locale-unabhängige Zeichenmengen bilden:
alnum | alphanumerische Zeichen, also Buchstaben und Ziffern |
alpha | Buchstaben |
ascii | ASCII-Zeichen |
blank | Leerzeichen und Tabulator |
cntrl | Steuerzeichen |
digit | Ziffern |
graph | druckbare Zeichen ([:print:]) ohne Leerzeichen |
lower | Kleinbuchstaben |
druckbare Zeichen inkl. Leerzeichen, also [:graph:] plus Leerzeichen | |
punct | Interpunktionszeichen: druckbare Zeichen, die weder zu den alphanumerischen
noch den Whitespace-Zeichen gehören:
` ^ ~ < = > | _ - , ; : ! ? ' " () [ ] { } @ $ * \ & # % +Bei aktivierter shopt-Option dotglob gehört zusätzlich der Punkt (.) zu den Interpunktionszeichen. |
space | Whitespace-Zeichen: Leerzeichen, Horizontal- und Vertikal-Tabulator, Newline (Zeilenschaltung), Carriage Return (Wagenrücklauf), Form Feed (Seitenvorschub) |
upper | Großbuchstaben |
word | alphanumerische Zeichen und Unterstreichungszeichen (_), also diejenigen Zeichen, aus denen man gültige Bezeichner bilden kann |
xdigit | Hexadezimal-Zeichen in Groß- und Kleinschreibung:
0 1 2 3 4 5 6 7 8 9 a A b B c C d D e E f F |
Um nun unabhängig von der Locale die Menge aller Kleinbuchstaben zu beschreiben, verwendet man die POSIX-Zeichenklasse lower:
# Ausgabe aller Dateinamen des aktuellen Verzeichnisses, die mit
# einem Kleinbuchstaben beginnen
echo [[:lower:]]*
Wie das Beispiel zeigt, ist für die Angabe einer POSIX-Zeichenklasse innerhalb einer Zeichenmengen-Definition die Syntax
[:Zeichenklassen-Name:]
zu verwenden.
Anmerkung: Es sei hier der Vollständigkeit halber erwähnt, dass noch zwei indirekte Möglichkeiten zur Zeichenklassen-Beschreibung existieren, die auf der Angabe eines Beispiel-Zeichens bzw. -Symbols beruhen. Nach Meinung des Autors sind sie für die tägliche Praxis aber ohne Belang. So kann man durch Angaben der Form [=c=] Äquivalenzklassen beschreiben. Das konkrete Beispiel würde alle Zeichen einschließen, die dasselbe Kollations-Gewicht wie das Zeichen c haben, also bei der Sortierung diesem gleichwertig sind. Mit der Angabe [.symbol.] wird die Ordnungsklasse beschrieben, deren Name dem Kollations-Symbol symbol entspricht. Ordnungsklassen können Zeichen und Zeichenfolgen enthalten. Alle Elemente einer Ordnungsklasse werden bzgl. der Sortierung als gleichwertig betrachtet (denkbar wären im Deutschen das Zeichen ä und die Zeichenfolge ae).
Das folgende Beispiel soll die Nutzung von Metazeichen sowie deren Quotierung veranschaulichen:
# Zunächst legen wir in einem neuen Verzeichnis mehrere Dateien an, die in # ihrem Namen Metazeichen von Pfadnamens-Mustern enthalten: mkdir neu cd neu touch a1b 'a?b' 'a??b' 'ab?c' 'a\' 'a\b' 'a\?b' 'a\?bc' 'ab\c' 'a\bc*' 'abcd' 'a[b]c' # Option nullglob zurücksetzen, so dass Muster, auf die kein # Pfadname passt, nicht gelöscht, sondern unverändert erhalten werden shopt -u nullglob # Die folgenden Muster enthalten Metazeichen sowie Quotierungen: echo ?\?* # Pfadnamen, die an der zweiten Stelle ein Fragezeichen haben: # a?b a??b echo ?\\?* # Pfadnamen, die an der zweiten Stelle einen Backslash # haben und mindestens 3 Zeichen lang sind: # a\b a\?b a\?bc a\bc* echo ?\\\?? # Pfadnamen, die an der zweiten Stelle einen Backslash # und an der dritten Stelle ein Fragezeichen haben sowie # genau 4 Zeichen lang sind: # a\?b echo *\* # Pfadnamen, die auf einen Stern enden: # a\bc* echo a\b # Hierbei handelt es sich nicht um ein Pfadnamens-Muster, # da keines der Metazeichen vorkommt. Somit wird die Zeichenfolge ab # ausgegeben. echo a[b?]* # Pfadnamen, die mit einem a beginnen, an zweiter # Stelle ein b oder ein ? haben: # a?b a??b ab?c ab\c abcd echo a[!b?]* echo a[^b?]* # Pfadnamen, die mit einem a beginnen und an zweiter # Stelle weder ein b noch ein ? haben: # a\ a1b a\b a\?b a[b]c a\?bc a\bc* echo a[b # Hierbei handelt es sich nicht um ein Pfadnamens-Muster, # da der Bereich nicht beendet ist, also die schließende Klammer ] fehlt. # Somit wird die Zeichenfolge a[b ausgegeben. echo a[?]c # Pfadnamen, die mit einem a beginnen, an zweiter # Stelle ein ? und an dritter Stelle ein c haben sowie genau # 3 Zeichen lang sind. Da es keinen passenden Namen gibt, wird das # Muster unverändert ausgegeben: a[?]c echo a\[?\]c # Pfadnamen, die mit der Zeichenfolge a[ beginnen und # mit der Zeichenfolge ]c enden, wobei zwischen diesen beiden # Zeichenfolgen genau ein beliebiges Zeichen steht: # a[b]c
Standardmäßig ist die Option dotglob nicht gesetzt. Dann muss ein Punkt am Anfang eines Musters oder unmittelbar hinter einem Schrägstrich explizit angegeben werden:
# alle Dateinamen (inkl. Punkt-Dateien) des aktuellen Verzeichnisses anzeigen echo .* * # alle Dateinamen des Verzeichnisses /tmp anzeigen echo /tmp/.* /tmp/* # durch Brace-Expandierung kann man das kürzer schreiben echo /tmp/{.*,*} # Option dotglob setzen shopt -s dotglob # jetzt kann der Punkt entfallen echo * echo /tmp/* # Hinweis: Die speziellen Einträge . und .. werden hier im Gegensatz # zum Muster .* nicht mit expandiert.
Weitere Beispiele finden sich im Abschnitt Pfadnamens-Expandierung.
Mit shopt -s extglob lassen sich erweiterte Musterangaben zuschalten, die aus der Korn-Shell stammen. Dabei steht Muster-Liste für eine durch Pipe-Striche (|) getrennte Liste von Mustern:
@(Muster-Liste) | deckt genau ein Vorkommen der angegebenen Muster ab |
+(Muster-Liste) | deckt mindestens ein Vorkommen der angegebenen Muster ab |
*(Muster-Liste) | deckt kein, ein oder mehrere Vorkommen der angegebenen Muster ab |
?(Muster-Liste) | deckt kein oder genau ein Vorkommen der angegebenen Muster ab |
!(Muster-Liste) | deckt die Strings ab, die durch keines der angegebenen Muster abgedeckt werden |
shopt -s extglob touch aaac aac abbc abc acc accc echo a@(a|b|c)c # aac abc acc echo a*(a|b|c)c # aaac aac abbc abc acc accc echo !(aa*|*cc) # abbc abc
Die o.g. POSIX-Zeichenklassen lassen sich auch bei den erweiterte Mustern verwenden.
Beispiel:
shopt -s extglob
# Dateinamen, die nur aus Kleinbuchstaben und Punkten bestehen,
# wobei auch hier die speziellen Einträge . und .. nicht mit
# expandiert werden.
echo +([[:lower:]]|.)
Die Bash unterstützt einfache (skalare) Variablen, die genau einen Wert speichern können, und eindimensionale Feld-Variablen (Felder, Arrays), in denen man eine Reihe von Werten ablegen kann, auf die man über Indexe zugreift. Manche Variablen/Felder werden von der Shell automatisch gepflegt bzw. steuern ihre Arbeit. Alle anderen können vom Shell-Anwender frei für eigene Zwecke eingesetzt werden.
Im Bash-Manual wird der Oberbegriff Parameter gebraucht. Ein Parameter ist ein Gebilde (entity), das Werte speichert. Er wird durch einen Namen, eine Zahl oder ein Sonderzeichen benannt. Bei Variablen handelt es sich lt. Manual um Parameter, die einen Namen haben. Dies trifft auch auf Felder bzw. Feld-Variablen zu. Als Variablen- bzw. Feld-Namen sind Bezeichner zulässig. Bei den durch Zahlen benannten Parametern handelt es sich um Positionsparameter. Die durch Sonderzeichen benannten Parameter bezeichnet man als spezielle Parameter. In der Literatur werden die Begriffe Parameter und Variable öfter auch synonym gebraucht.
Eine nutzereigene einfache Variable wird dynamisch angelegt, wenn man ihr über das Konstrukt
Variablen-Name=[Wert]
einen Wert zuweist. Analog wird ein Feld dynamisch angelegt, wenn man einem Feld-Element durch das Konstrukt
Feld-Name[Index]=[Wert]
einen Wert zuweist. Der Wert ist jeweils optional. Wenn man ihn weglässt, wird die leere Zeichenkette zugewiesen.
Felder haben keine festgelegte Maximalgröße. Sie werden durch nichtnegative ganze Zahlen (0 bis n) indexiert, wobei die Index-Werte nicht lückenlos vergeben werden müssen.
Beispiele:
name=Anton feld[3]=Berta
Dadurch wird der Variablen name der Wert Anton und dem Element mit Index 3 der Feld-Variablen feld der Wert Berta zugewiesen. Das Feld feld hat nach dieser Zuweisung nur dieses eine Element, sofern es vor der Zuweisung noch nicht existierte.
Wenn man einer existierenden Feld-Variablen durch ein Konstrukt der Form
Feld-Name=[Wert]
einen Wert zuweist, so entspricht dies einer Zuweisung an das Element mit Index 0. Würde man der oben angelegten Feld-Variablen feld durch die Anweisung
feld=Dora
den String Dora zuweisen, wäre dies analog zu
feld[0]=Dora
Damit hätte feld nun 2 Elemente: feld[0] mit dem Inhalt Dora und feld[3] mit dem Inhalt Berta.
Anmerkung: Will man den Namen einer existierenden Feld-Variablen nachfolgend als Name einer einfachen Variablen verwenden, genügt es also nicht, dem unindexierten Namen einen String als Wert zuzuweisen. Es ist vorher erforderlich, das Feld mit dem Kommando unset zu löschen. Hier ein Beispiel:
unset feld feld='Wert einer einfachen Variablen'
Man kann auch einzelne Feld-Elemente löschen, indem man bei unset hinter dem Feld-Namen einen Index angibt, z.B.:
unset feld[0]
Hier wird nur das erste Feld-Element (mit Index 0) gelöscht. Wenn man als Index die speziellen Werte @ und * verwendet, wird wie beim kompletten Verzicht auf eine Index-Angabe das gesamte Feld gelöscht.
Bei den in eckigen Klammern angegebenen Feld-Indexen muss es sich nicht um ganzzahlige Konstanten handeln. Allgemein sind arithmetische Ausdrücke zulässig, z.B. so:
i=8 feld[2*i]=99
Dadurch wird dem Feld-Element mit Index 16 der Wert 99 zugewiesen.
Mit einer zusammengesetzten Zuweisung der Form
Feld-Name=([Wert1 ... Wertn])
kann man eine Feld-Variable initialisieren, ihr also die angegebenen Werte als Feld-Elemente zuweisen, wodurch ein nicht existentes Feld natürlich ebenfalls angelegt wird. Ein evtl. zuvor existierender Inhalt einer gleichnamigen einfachen oder Feld-Variablen geht dabei verloren. Mit einer solchen Zuweisung kann man also aus einer einfachen Variablen eine Feld-Variable machen, z.B. so:
# Variable abc löschen unset abc # Variable abc als einfache Variable initialisieren abc='einfache Variable' # Variable abc anzeigen declare -p abc # Anzeige: declare -- abc="einfache Variable" # Variable abc als Feld initialisieren und anzeigen abc=(1 2 3) declare -p abc # Anzeige: declare -a abc='([0]="1" [1]="2" [2]="3")' # Die Variable abc ist nun eine Feld-Variable und keine einfache # Variable mehr.
Bei der Feld-Initialisierung hat jeder Wert die allgemeine Form
[Index]=String
Es genügt allerdings auch, nur den String anzugeben. Der String muss aus Sicht der Shell generell ein separates Wort sein. Strings, die aus mehreren Wörtern bestehen, weil sie Worttrenner enthalten, sind daher geeignet zu quotieren. Die Quotierung ist natürlich auch nötig, wenn die Sonderbedeutung von Metazeichen aufgehoben werden soll.
Ein explizit angegebener Index benennt das Feld-Element, dem der nachfolgende String zuzuweisen ist. Ein nur aus dem String bestehender Wert wird demjenigen Element zugewiesen, dessen Index um eins größer als der Index des Elements ist, dem zuletzt ein Wert zugewiesen wurde. Beim ersten Wert hat dieser implizite Index den Wert 0.
Beispiele:
feld=(1 2 3) # Variable feld mit den 3 Elementen 1, 2 und 3 # initialisieren; es werden die impliziten Indexe 0, 1 und 2 verwendet # Mit expliziter Indexierung ließe sich dieselbe Initialisierung so schreiben: feld=([0]=1 [1]=2 [2]=3) feld_zwei=(\$ '2 3' '`<>\') # Da hier die Feld-Elemente Worttrenner und sonstige Metazeichen enthalten, # nutzen wir eine geeignete Quotierung: # Element 0: $ # Element 1: 2 3 # Element 2: `<>\
Bei Aufruf des Kommandos set ohne Argumente werden u.a. die Belegungen aller Variablen und Felder ausgegeben, und zwar in Form von Zuweisungen, mit denen man diese Variablen/Felder mit ihrem derzeitigen Wert initialisieren würde. Für unser obiges feld generiert set folgende Ausgabe:
feld=([0]="1" [1]="2" [2]="3")
Man kann sie gezielt durch
set | grep '^feld='
herausfiltern. Alternativ kann man den oben schon benutzen Weg
declare -p feld
verwenden, der allerdings eine etwas andere Ausgabe generiert:
declare -a feld='([0]="1" [1]="2" [2]="3")'
Hier noch ein anderes Beispiel für eine Feld-Initialisierung, wobei explizite und implizite Indexierung zusammen verwendet werden:
feld=([9]=1 2 [14]=3)
# dies entspricht folgender Anweisung:
# feld=([9]="1" [10]="2" [14]="3")
Mit dem Kommando declare kann man unter Verwendung der Option -a Variablen explizit als Feld-Variablen deklarieren, womit man sie gleichzeitig anlegt, falls sie nicht existieren. So deklariert man z.B. mit
declare -a feld
das leere Feld feld, sofern diese Variable noch nicht existierte. Wenn feld schon eine Feld-Variable war, wird durch declare nichts verändert. War feld dagegen eine skalare Variable, dann wird sie in eine Feld-Variable umgewandelt, wobei der bisherige Wert zum Wert des Feld-Elements mit Index 0 wird.
Anmerkungen:
Stattdeclare -a Name
kann man auchdeclare -a Name[Index]schreiben. Der Index wird dabei ignoriert. Beide Angaben sind demnach identisch.
Mit den Kommandos declare und readonly kann man einer Feld-Variablen Attribute zuweisen. Diese gelten dann für alle Elemente des Feldes, z.B.:
declare -a -i -r feld=(12*3 9+7 40/8) # Durch -i wird feld als Feld ganzer Zahlen vereinbart. # Zuweisungen an die Elemente werden daher arithmetisch evaluiert. # Die Option -r besagt, dass die Elemente des Feldes nur lesbar sind. # Nach der Initialisierung sind deshalb keine Zuweisungen mehr möglich. # Auch eine Feld-Löschung mit unset wird von der Bash verhindert. # Das Attribut 'nur lesbar' lässt sich auch nicht wieder entfernen. # # äquivalente Anweisung: # declare -air feld='([0]="36" [1]="16" [2]="5")' # Ein Feld kann auch nachträglich mit dem Attribut 'nur lesbar' versehen # werden: declare -r feld # oder readonly feld
Auch bei declare kann man die obige Mehrfach-Zuweisung zur Initialisierung von Feldern nutzen. Außerdem kann man mit einer declare-Anweisung mehrere Felder anlegen und initialisieren, was folgendes Beispiel zeigt:
declare -a feld=(2 3 4) feld1=([9]=1 2 [14]=3) feld2
# dies entspricht:
# feld=([0]="2" [1]="3" [2]="4")
# feld1=([9]="1" [10]="2" [14]="3")
# feld2=()
Mehrere Wertzuweisungen können auf einer Zeile stehen. Sie sind jeweils durch mindestens ein Leerzeichen oder einen Tabulator voneinander zu trennen. Ein Semikolon als Trenner ist nicht nötig. Hier ein Beispiel:
var1=wert1 var2= var3=wert3 ... feld1=(wert1 wert2 wert3) feld2=() feld3=(wert4 wert5 wert6) ...
Wichtig: Vor dem Zuweisungs-Operator (d.h. dem Gleichheitszeichen) darf kein Leerraum stehen. Als Wert wird immer das erste Wort hinter dem Gleichheitszeichen verwendet, das auch leer sein kann. Wenn es sich beim Wert um einen String handelt, der Worttrenner enthält, ist er geeignet zu quotieren.
Variablenwerte haben keine festgelegte Maximalgröße. Die Shell stellt den jeweils benötigten Speicherplatz dynamisch zur Verfügung. Das gesamte Speichermanagement obliegt komplett der Shell. Der Nutzer muss sich darum nicht kümmern und hat keinen Einfluss darauf.
Werte von Variablen und Feld-Elementen sind generell Strings. Bei einer arithmetischen Evaluierung werden sie automatisch als Zahlen interpretiert. Bash-Strings werden intern durch ein NUL-Byte (Byte mit dem Dezimal-Wert 0) beendet, wie dies in der Sprache C auch üblich ist. Daher können Bash-Strings keine NUL-Bytes enthalten.
Das Sonderzeichen $ vor einem Parameter-Namen leitet eine Parameter- bzw. Variablen-Expandierung ein. Ausdrücke der Form
$Parameter-Name ${Parameter-Name}
werden von der Shell durch den Inhalt des genannten Parameters ersetzt, so dass man mit ihnen einen Parameter bzw. eine Variable referenzieren, d.h. auf ihren Inhalt bzw. Wert zugreifen kann, wie folgende Beispiele zeigen:
$var # Inhalt der Variablen var ${var} # Inhalt der Variablen var ${feld[3]} # Inhalt des Elements mit Index 3 des Feldes feld $7 # Inhalt des Positionsparameters 7 ${11} # Inhalt des Positionsparameters 11 $$ # Prozess-ID des aktuellen Prozesses
Wenn der referenzierte Parameter nicht existiert, so wird standardmäßig das gesamte Konstrukt stillschweigend (also ohne Meldung) durch den leeren String ersetzt. Bei gesetzter set-Option u bzw. nounset prüft die Bash dagegen, ob ein referenzierter Parameter existiert. Wenn nicht, erscheint eine Fehlermeldung der Form
var: unbound variable
Eine nicht-interaktive Shell wird danach abgebrochen.
Durch die Option nounset kann man sich davor schützen, dass man aus Versehen den leeren String als Wert einer ungesetzten Variablen verwendet. Allerdings muss man ggf. seine Skripten anpassen, wenn man die Option nutzen möchte. Hier ein Beispiel:
# Variable var löschen unset var # Option nounset setzen set -u # testen, ob Variable var leer ist # Variante 1, die bei deaktivierter Option nounset funktioniert, hier # aber eine Fehlermeldung bewirkt, weil innerhalb des Tests mit # dem Konstrukt $var ein nicht existierender Parameter referenziert # wird: [[ -z $var ]] && echo leer # Variante 2, die wie gewünscht funktioniert, weil das # zum Test auf leere bzw. nicht existierende Parameter # dienende Konstrukt ${var-} im Falle der Nicht-Existenz von # var durch den leeren String ersetzt wird: [[ -z ${var-} ]] && echo leer
Beim Zugriff auf Feld-Elemente sowie auf Positionsparameter, deren Nummer größer als 9 und damit nicht mehr einstellig ist, müssen die geschweiften Klammern in oben gezeigter Weise verwendet werden. Bei Feldern sind die Klammern erforderlich, um Konflikte mit der Pfadnamens-Expandierung zu vermeiden. Bei einfachen Variablen sind sie dagegen nur dann nötig, wenn hinter dem Parameter-Namen ein Zeichen folgt, das syntaktisch noch zum Namen gehören könnte, also ein alphanumerisches Zeichen oder ein Unterstrich (_). Um Missverständnisse sicher zu vermeiden, kann man die geschweiften Klammern auch generell setzen, um der Shell exakt mitzuteilen, bis wohin sich der Parameter-Name erstreckt.
Bei Verwendung der speziellen Feldindexe @ und * wird das Konstrukt durch alle Elemente des betreffenden Feldes substituiert. Diese beiden Indexe verhalten sich nur dann unterschiedlich, wenn das Konstrukt in Doppelapostrophe eingeschlossen ist. Dann wird
"${Feld-Name[*]}"
zu einem einzigen Wort expandiert, das alle Feld-Elemente enthält. Diese sind jeweils durch das erste Zeichen der Variablen IFS voneinander getrennt. Wenn IFS leer ist, wird ein einzelnes Leerzeichen als Trenner genutzt. Hat IFS den leeren String als Wert, wird gar kein Trenner eingefügt.
Bei
"${Feld-Name[@]}"
wird dagegen jedes Feld-Element zu einem separaten Wort expandiert. Wenn das Feld keine Elemente besitzt, wird das gesamte Konstrukt zum leeren String expandiert.
Der Ausdruck
${#Feld-Name[Index]}
expandiert zur Länge des Feld-Elements ${Name[Index]}. Bei Verwendung der speziellen Indexe @ und * wird der Ausdruck zur Anzahl der Feld-Elemente expandiert.
Wenn man eine Feld-Variable ohne Index-Angabe referenziert, wird immer das Element mit Index 0 angesprochen.
Bei einer Wert-Zuweisungen an eine Variable unterliegen die Werte einer Tilde- und Parameter-Expandierung, einer Kommando-Substitution, einer arithmetischen Expandierung sowie der Entfernung von Quotierungs-Zeichen. Eine Aufspaltung in Wörter erfolgt dagegen außer beim Wert
"$@"
(Liste aller Positionsparameter) nicht. Daher kann man den Wert einer Variablen einer anderen Variablen ohne Quotierung zuweisen:
var1="1 2 3"
var2=$var1 # var2="$var1" ist auch möglich
Eine Pfadnamens-Expandierung erfolgt bei der Zuweisung ebenfalls nicht.
Anmerkung: Das bei der Feld-Initialisierung in runde Klammern eingeschlossene Konstrukt, also die Folge der Wert-Zuweisungen an die Feld-Elemente, unterliegt standardmäßig einer Aufspaltung in Wörter, wobei ganz normal die in der Variablen IFS enthaltenen Zeichen als Wortrenner fungieren. Dies ist speziell dann zu beachten, wenn diese Wörter aus einer Expandierung stammen, wie folgendes Beispiel zeigt:
unset IFS # IFS auf den Standard-Wert setzen feld=($(echo $'1 2\n3 4\n5 6')) # Diese Zuweisung intialisiert ein sechselementiges Feld. Die Zahlen von 1 bis # 6 sind die Feld-Elemente: # feld=([0]="1" [1]="2" [2]="3" [3]="4" [4]="5" [5]="6") # Möchte man statt dessen die einzelnen vom Kommando echo ausgegebenen # Zeilen als separate Feld-Elemente speichern, ließe sich dies erreichen, # indem man der Variablen IFS ein Newline zuweist: IFS=$'\n' feld=($(echo $'1 2\n3 4\n5 6')) # entspricht: feld=([0]="1 2" [1]="3 4" [2]="5 6")
Zuweisungs-Anweisungen können auch als Argumente der eingebauten Kommandos alias, declare, typeset, export, readonly und local auftreten.
Man kann eine Variable mit
declare -i Variablenname
explizit als Integer-Variable (Ganzzahl-Variable) deklarieren, was zur Folge hat, dass Zuweisungen an diese Variable arithmetisch evaluiert werden. Der Wert einer Integer-Variablen ist aber weiterhin ein String.
Variablen, Feld-Elemente und komplette Felder können, sofern sie nicht mit dem Attribut readonly (nur lesbar) gekennzeichnet sind, jederzeit durch eine weitere Zuweisung modifiziert und durch das eingebaute Kommando unset vernichtet werden.
Positionsparameter sind spezielle Variablen, die die Argumente eines Shell-Skripts oder einer Shell-Funktion beinhalten. Sie sind mit 1 beginnend durchnummeriert und werden mittels Parameter-Expandierung durch Konstrukte der Art
$1 $5 ${11}
angesprochen. Wie schon erwähnt, müssen Nummern größer als 9 in geschweifte Klammern eingeschlossen werden, da ohne Klammern nur einstellige Nummern akzeptiert werden.
Innerhalb eines Skripts oder einer Funktion kann man die Positionsparameter unter Verwendung des eingebauten Kommandos set neu belegen. Eine Wertzuweisung mit dem Gleichheitszeichen ist dagegen nicht möglich.
Wird set ohne Argumente aufgerufen, dann gibt es alle existierenden Shell-Variablen zusammen mit ihren Werten in Form von Zuweisungen aus, die die aktuellen Werte an die betreffenden Variablen zuweisen würden, z.B.:
EDITOR=vim
Die Nutzung von set zum Setzen und Rücksetzen von Shell-Optionen wird im Abschnitt Modifikation von Shell-Optionen in einer laufenden Shell beschrieben. Mit einem set-Kommando kann man gleichzeitig oder getrennt Optionen verändern und Positionsparameter setzen oder löschen.
Wenn Optionen und Positionsparameter gleichzeitig verändert werden, dann müssen die Optionen immer vor den Werten der Positionsparameter stehen, z.B.:
set -x 1 2 3
# Option x aktivieren sowie die ersten 3 Positionsparameter
# neu setzen und evtl. weiter hinten folgende löschen:
# 1 --> $1
# 2 --> $2
# 3 --> $3
Eine solche mit set realisierte Zuweisung an die ersten n Positionsparameter löscht evtl. vorhandene weitere Positionsparameter (im Beispiel also ab Positionsparameter 4).
Sofern der zu setzende neue Wert des ersten Positionsparameters mit einem Minus-Zeichen beginnt und somit eine Option sein könnte, so muss man die Options-Liste von der Liste der Positionsparameter-Werte trennen. Dies kann durch zwei spezielle Optionen geschehen:
-- (zwei Minus-Zeichen) - (ein Minus-Zeichen)
Wenn hinter -- keine Argumente folgen, werden alle existieren Positionsparameter gelöscht. Wenn dagegen bei - keine Argumente folgen, bleiben die Positionsparameter unverändert. Alle hinter - oder -- folgenden n Argumente werden den ersten n Positionsparametern zugewiesen. Evtl. weiter hinten folgende Positionsparameter werden dabei wie beschrieben gelöscht. Bei Verwendung der Option - werden automatisch die Optionen x und v deaktiviert.
Mit dem eingebauten Kommando
shift [n]
kann man die Positionsparameter um n Positionen nach links verschieben, wodurch die bisherigen ersten n Positionsparameter vernichtet werden und der Parameter $#, der die Anzahl der Positionsparameter angibt, um n vermindert wird. Wenn n fehlt, wird eine 1 angenommen.
Das Argument n muss eine Zahl zwischen 1 und $# sein, um eine Verschiebung zu bewirken. Wenn n den Wert 0 hat oder größer als $# ist, erfolgt keine Verschiebung.
Hier ein Beispiel:
# 7 Positionsparameter setzen set a b c d e f g # alle Positionsparameter löschen set -- # Positionsparameter 1 auf den Wert -a setzen set -- -a # set-Optionen -x und -v bleiben unverändert set - -a # set-Optionen -x und -v werden zurückgesetzt # Option x setzen und alle Positionsparameter löschen set -x -- # Option x setzen und die ersten drei Positionsparameter # auf die Werte # 1 # -2 # -4 # setzen (und den Rest automatisch löschen) set -x 1 -2 -4 # Option x setzen und die ersten zwei Positionsparameter # auf die Werte # -1 # 2 # setzen (und den Rest automatisch löschen) set -x - -1 2 # set-Optionen -x und -v werden zurückgesetzt; # -x wird also zuerst gesetzt und dann gleich wieder deaktiviert set -x -- -1 2 # set-Optionen -x und -v bleiben unverändert # Positionsparameter um 3 Stellen nach links verschieben shift 3 # die Positionsparameter lauten nun: d e f g # $# hat jetzt den Wert 4
Unter Nutzung von shift und $# kann man z.B. folgendermaßen die Argumente eines Shell-Skripts in einer Schleife durchlaufen:
while (($#))
do
# Der Schleifenkörper wird für jeden Positionsparameter 1 Mal durchlaufen.
# Durch shift stehen der Reihe nach alle Positionsparameter unter $1 zur Verfügung.
((++i))
echo "Argument $i: $1"
shift
done
Neben den Positionsparametern gibt es auch noch spezielle Parameter, die von der Shell automatisch gepflegt werden. Auf sie kann man nur lesend zugreifen, d.h., die explizite Zuweisung eines Wertes an diese Parameter ist nicht möglich. Der Zugriff erfolgt wieder über die Parameter-Substitution, wobei dem $ hier ein spezielles Zeichen folgt, das den betreffenden Parameter benennt.
Die folgende Tabelle zeigt die speziellen Parameter der Bash, wobei gleich der Ausdruck angegeben wird, den man zum Zugriff auf den betreffenden Parameter benötigt:
$* | Expandierung aller Positionsparameter
Wenn dieser Ausdruck in doppelte Apostrophe eingeschlossen wird, dann entsteht genau ein Wort, das der Reihe nach alle Positionsparameter enthält, die jeweils durch das erste Zeichen der Variablen IFS getrennt sind, also "$1c$2c...", wenn c das erste Zeichen von IFS ist. Falls IFS ungesetzt ist, wird ein Leerzeichen als Trenner verwendet: "$1 $2 ...". Wenn IFS die leere Zeichenkette als Wert hat, dann wird kein Trenner eingesetzt: "$1$2...". Wenn es keine Positionsparameter gibt, dann expandiert $* zu nichts (das Konstrukt wird also gelöscht) und "$*" zum leeren String. |
$@ | Expandierung aller Positionsparameter
Wenn dieser Ausdruck in doppelte Apostrophe eingeschlossen wird, dann wird jeder Positionsparameter als ein separates Wort expandiert. "$@" entspricht also "$1" "$2" ...Wenn es keine Positionsparameter gibt, dann expandieren $@ und "$@" zu nichts, d.h., sie werden gelöscht. |
$# | Anzahl der Positionsparameter |
$? | Exit-Status der zuletzt im Vordergrund ausgeführten Pipeline |
$- | Liste der aktuellen einbuchstabigen Shell-Optionen |
$$ | Prozess-ID der Shell
Innerhalb einer durch () gestarteten Sub-Shell wird weiterhin die Prozess-ID der aktuellen (Eltern-)Shell und nicht die Prozess-ID der Sub-Shell gemeldet. |
$! | Prozess-ID des zuletzt gestarteten Hintergrund-Kommandos |
$0 | Name der aktuellen Shell oder des aktuellen Shell-Skripts (Argument 0 der Argumente-Liste des aktuellen Prozesses) |
$_ | nach dem Shell-Start: absoluter Pfadname der aktuellen Shell gemäß
Argumente-Liste
nachfolgend: letztes Argument des vorherigen Kommandos in expandierter Form |
Beispiele:
# Wertzuweisung für einfache Variablen name='Normalverbraucher, Otto' zahl=45 # bei der folgenden Wertzuweisung ist eine Quotierung möglich, aber # nicht nötig, da die Shell den Wert von name nicht in Wörter aufteilt: name2=$name name2="$name" # hier ist eine Quotierung dagegen nötig: name3="$name $zahl" # Variable löschen unset zahl # Initialisierung eines Feldes; die Feld-Elemente mit den Index-Werten # 0, 1, 2 werden mit den Strings a, b und c belegt; weitere Elemente # hat das Feld danach nicht feld=(a b c) # Ausgabe von Feld-Element 0 echo $feld echo "$feld" echo "${feld[0]}" # Wertzuweisung an Feld-Element 3 feld[3]=d # Löschung von Feld-Element 2 unset feld[2] # Vernichtung des gesamten Feldes unset feld # Zuweisung des leeren Feldes (0 Elemente) feld=() # Zugriff auf einfache Variablen echo "$name" echo "${name}kar" # Zugriff auf Felder # Feld in Liste umwandeln for i in "${feld[@]}" do echo "$i" done echo ==== # über Feldindexe zugreifen for i in 0 1 2 3 do echo "${feld[$i]}" done echo ==== # in Zählschleife über Feldindexe zugreifen for (( i = 0; i < ${#feld[@]}; i++ )) do echo "${feld[$i]}" done echo ==== # musterbasierte Zeichenersetzung auf alle Feld-Elemente anwenden # alle Dateinamen unter /etc in das Feld files übernehmen files=(/etc/*) # in allen Feld-Elementen von links alle Zeichen bis zum letzten # Schrägstrich streichen und so nur den eigentlichen Dateinamen belassen # (logisch also das Kommando basename anwenden) echo ${files[@]##*/} # Positionsparameter neu setzen: # $1 --> 1 # $2 --> 2 3 # $3 --> 4 # Da $2 einen Wert bekommen soll, der ein Leerzeichen enthält, muss man ihn # geeignet quotieren. set 1 '2 3' 4 # alle Positionsparameter in einer Schleife ausgeben for i in "$@" do echo "$i" done # Wenn es keine Positionsparameter gibt, wird der Körper dieser # Schleife nicht (also 0 Mal) ausgeführt. Gleiches gilt, wenn man statt # "$@" die Ausdrücke $@ und "$@" angeben würde, # weil in allen 3 Fällen die Liste hinter in leer ist. # # Dagegen wird die folgende Schleife ein Mal durchlaufen, wobei i den # leeren String als Wert hat: for i in "$*" do echo "$i" done # aktuelle Prozess-ID und Exit-Status des letzten Vordergrund-Kommandos ausgeben echo $$ $?
Über Umgebungsvariablen lassen sich Daten an Kind-Prozesse weitergeben (als Alternative zu Dateien und Pipes). Manche Umgebungsvariablen werden von der Shell selbst ausgewertet bzw. gepflegt und steuern ihre Arbeit. Alle anderen sind frei in Shell-Kommandos verwendbar. Mit der Shell kann man die Umgebungsvariablen des Shell-Prozesses bequem auslesen und meist auch verändern. Auf sie wird wie auf normale Variablen zugegriffen.
Beispiele:
# komplette Anzeige der Umgebung env printenv # gezielte Anzeige, z.B. Umgebungsvariable LC_ALL echo "$LC_ALL" # Der Zugriff erfolgt also wie bei normalen Variablen mittels # Parameter-Substitution. # Variablen können mit dem eingebauten Kommando export bzw. declare # exportiert, also zu Umgebungsvariablen erklärt werden. Wenn man ihnen beim Export # oder nachfolgend Werte zuweist, schreibt man diese automatisch in die Umgebung: export XYZ # Umgebungsvariable XYZ exportieren; damit wird sie aber noch nicht angelegt XYZ=abc # der Umgebungsvariablen XYZ einen Wert zuweisen und sie damit anlegen # Das Kommando export beherrscht Wert-Zuweisungen. # Dies war bei der Bourne-Shell noch nicht der Fall. # LC_ALL dauerhaft auf C setzen, was u.a. englische Fehlermeldungen # bei den nachfolgenden Kommandos bewirkt export LC_ALL=C rm / # meldet # rm: cannot remove directory `/': Is a directory # statt # rm: Entfernen von Verzeichnis „/“ nicht möglich: Ist ein Verzeichnis # Die Umgebungsvariablen, die einem Shell-Prozess übergeben wurden, sind # automatisch exportiert, typischerweise z.B. HOME. Um sie zu verändern, # reicht also eine Wertzuweisung aus. export kann benutzt werden, ist # aber unnötig: HOME=/tmp export HOME=/tmp # Anzeige aller exportierten Variablen export -p # Man kann auch Funktionen exportieren: function abc() { echo ABC; } export -f abc # Eine Funktion wird über eine Umgebungsvariable weitergegeben, die # den Namen der Funktion trägt. Im obigen Beispiel sähe das so aus: # abc=() { echo ABC # } # Die Variable heißt also abc. Ihr Wert beginnt mit (). # Hinter ABC kommt ein Newline-Zeichen, da dieses an Stelle # des Semikolons gespeichert wurde. Daher steht die abschließende # Klammer in der Anzeige auf einer neuen Zeile. # Liste der exportierten Funktionen anzeigen export -pf # mit Funktions-Definition export -pF # ohne Funktions-Definition # Löschung von Exporten export -n LC_ALL # Variable LC_ALL export -nf abc # Funktion abc # Damit wird nur der Export aufgehoben. Die Variablen und Funktionen bleiben # aber erhalten. Derartige Variablen existieren dann als normale Shell-Variablen weiter. # Nicht mehr exportierte Funktionen werden beim Aufruf von Kommandos nicht mehr an diese # weitergegeben. # Alternativ-Notation für Export declare -x # Anzeige aller exportierten Variablen declare -xf # Anzeige aller exportierten Funktionen mit Definition declare -xF # Anzeige aller exportierten Funktionen ohne Definition
Unter Quotierung (Quoting) versteht man die Aufhebung der Sonderbedeutung von Zeichen und Wörtern. Die folgende Tabelle listet eine Vielzahl allgemeiner Sonderzeichen (Metazeichen) der Bash auf und erläutert deren typische Bedeutung:
Leerzeichen und Tabulator | Trennzeichen für Wörter |
Newline | Zeilenende; Übergabe der Zeile an die Shell zur Abarbeitung |
> | Ausgabe-Umlenkung |
>> | Ausgabe-Umlenkung mit Anhängung an eine Datei |
>| | Ausgabe-Umlenkung mit Überschreiben von Dateien trotz Option noclobber |
&> >& |
gemeinsame Umlenkung von stdout und stderr |
< | Eingabe-Umlenkung |
<> | Datei zum Lesen und Schreiben öffnen |
<&
>& |
Duplizierung von Deskriptoren |
<&...-
>&...- |
Verschiebung von Deskriptoren |
<< | Hier-Dokument |
<<< | Hier-String |
| | Pipe |
* | Platzhalter für einen beliebigen (auch leeren) String bei der Pfadnamens-Expandierung |
? | Platzhalter für genau 1 Zeichen bei der Pfadnamens-Expandierung |
[...] | Zeichenmenge bei der Pfadnamens-Expandierung
Durch [!...] oder [^...] wird eine negierte Zeichenmenge beschrieben. |
?(...) *(...) +(...) @(...) !(...) |
erweiterte Muster bei der Pfadnamens-Expandierung (bei gesetzter Option extglob) |
~ | Home-Verzeichnis |
; | Trennzeichen zwischen sequentiell auszuführenden Shell-Kommandos |
& | Hintergrund-Kommando |
`...` | Backtick/Backquote; veraltete Form der Kommando-Substitution |
$(...) | neue Form der Kommando-Substitution |
(...) | Liste, die in einer Sub-Shell auszuführen ist |
{ ...; } | Liste, die in der aktiven Shell auszuführen ist
Die geschweiften Klammern sind genau genommen keine Metazeichen, sondern Schlüsselwörter der Shell. |
$ | Präfix der Variablen-/Parameter-Expandierung:
$0 $9 ${10} $* $@ $$ $var ${var} ${var##*a} ${var:-leer} $feld[index]} $feld[@]} ... |
$((...)) | arithmetische Expandierung |
\ | quotiert das Folgezeichen |
'...' | starke Quotierung der in Apostrophe eingeschlossenen Zeichen |
"..." | schwache Quotierung der in Doppelapostrophe eingeschlossenen Zeichen |
= | Wertzuweisungen an Variablen und Alias-Namen |
&& | Kommando-Trenner einer UND-Liste |
|| | Kommando-Trenner einer ODER-Liste |
# | Beginn eines Kommentars |
((...)) | arithmetische Auswertung eines Ausdrucks |
/ | Verzeichnis-Trennzeichen in Pfadnamen |
! | History-Expandierung und logische Negation bei Pipes |
^ | als erstes Zeichen der Kommandozeile: Quick-Substitution bei der History-Expandierung |
: | Präfix für History-Modifikatoren |
In speziellen Situationen (z.B. bei regulären Ausdrücken) kann es weitere Sonderzeichen geben. Manche Sonderzeichen haben je nach Kontext auch weitere Bedeutungen, die oben nicht genannt sind.
Für die Quotierung existieren folgende Möglichkeiten:
Backslash \ | Aufhebung der Sonderbedeutung des Folgezeichens |
einfache Apostrophe '...' | starke Quotierung: Aufhebung der Sonderbedeutung aller Zeichen zwischen den begrenzenden Apostrophen |
doppelte Apostrophe "..." | schwache Quotierung: Aufhebung der Sonderbedeutung der meisten Zeichen
zwischen den begrenzenden Doppelapostrophen. Folgende drei Zeichen behalten
ihre Sonderbedeutung:
$ ` \Der Backslash behält seine Sonderbedeutung vor $ ` " \ Newline |
Wörter der Form $'string' | ANSI-C-Quotierung: Interpretation von ANSI-C-Escape-Sequenzen (s. unten) |
Wörter der Form $"string" | Locale-spezifische String-Übersetzung |
Mit dem Backslash und einfachen Apostrophen kann man folglich alle Arten der Expandierungen unterbinden. Mit Doppelapostrophen bleiben History-Expandierungen sowie Variablen-, Kommando- und arithmetische Expandierungen aktiv.
Die Zeichenfolge Backslash Newline gestattet die logische Fortsetzung der aktuellen Kommandozeile auf der folgenden Eingabe-Zeile. Damit lassen sich lange logische Zeilen übersichtlicher gestalten, weil man sie auf mehrere physische Zeilen verteilen kann. Falls eine Zeile mit einem Pipe-Strich, der Zeichenfolge && oder || endet (wenn diesen also unmittelbar das Newline-Zeichen folgt), wird die nächste Zeile bei Shells der Bourne-Shell-Familie (im Gegensatz zur C-Shell-Familie) automatisch als logische Fortsetzung betrachtet. Eine explizite Kennzeichnung durch Backslash Newline ist dann also nicht nötig.
# Hilfs-Funktion zur Anzeige aller ihrer Argumente auf einer separaten Zeile function show_args() { local nr=0 for arg # Kurzform für: for arg in "$@" do echo "$((++nr)): |$arg|" done echo '--------' } # lange logische Zeile durch Backslash Newline physisch teilen echo lange \ Zeile # lange logische Zeile durch |, &&, || physisch teilen ls | wc -l true && echo true false || echo true # Nutzung verschiedener Quotierungs-Möglichkeiten echo 'ho* [a-c]*' # Ausgabe: ho* [a-c]* show_args a\ b show_args 'a b' show_args "a b" # Ausgabe: a b name=Otto echo "\$name hat den Wert '$name'" # Ausgabe: $name hat den Wert 'Otto' echo '"`$\'\' # Ausgabe: "`$\' # Wie das letzte Beispiel zeigt, kann man quotierte Strings auch lückenlos # aufeinander folgen lassen. Die Shell verkettet sie dann auch lückenlos. # Dies wollen wir uns an einem weiteren Beispiel nochmal genauer ansehen: echo 'Der Inhalt von $name lautet'\ "'$name'" # Dem durch (rot markierte) einfache Apostrophe quotierten String folgt hier # ein mittels Backslash quotiertes Leerzeichen sowie anschließend ein durch # (hellgrün markierte) Doppelapostrophe quotierter String. An das Kommando # echo wird folglich genau ein Wort mit dem Inhalt # # Der Inhalt von $name lautet 'Otto' # # übergeben. # # Das durch Backslash quotierte Leerzeichen wurde nur zu # Demonstrationszwecken eingefügt. In der Praxis würde man es sicher nicht # notieren, weil man ja das Leerzeichen auch bequem durch die nachfolgenden # Doppelapostrophe schützen kann. # # Doppelapostrophe kann man also nutzen, um einfache Apostrophe zu # quotieren. Analog kann man einfache durch Doppelapostrophe quotieren. # Statt dieser wechselseitigen Quotierung kann man auch Backslash-Folgen # innerhalb von Doppelapostrophen verwenden, was bei Ausdrücken mit vielen # Backslash-Sequenzen evtl. etwas unübersichtlich wird. # # Obige Ausgabe könnte man auch so erreichen: echo "Der Inhalt von \$name lautet '$name'"
Die Bash unterstützt die ANSI-C-Quotierung. D.h., Wörter der Form $'string' gestatten die Nutzung von ANSI-C-Sequenzen:
\a | Alert (Bell; akustisches Signal) |
\b | Backspace (Rückschritt) |
\e | Escape |
\f | Formfeed (Seitenvorschub) |
\n | Newline (Zeilenvorschub) |
\r | Carriage Return (Wagenrücklauf) |
\t | Horizontal-Tabulator |
\v | Vertikal-Tabulator |
\\ | Backslash |
\’ | Apostroph |
\nnn | Oktalzahl (1 bis 3 Ziffern) |
\xHH | Hexadezimal-Zahl (1 oder 2 Ziffern) |
\cx | Ctrl-x |
Beispiel:
echo $'\nHallo\nWelt'
# Vor und hinter Hallo wird ein Zeilenvorschub ausgegeben.
Beispiel für eine Locale-spezifische String-Übersetzung in der Locale de_DE.UTF-8:
# Nutzung des Message-Katalogs /usr/share/locale/de/LC_MESSAGES/tar.mo export TEXTDOMAINDIR=/usr/share/locale/de/LC_MESSAGES export TEXTDOMAIN=tar echo $"stdout" # Ausgabe des Strings Standardausgabe als deutsche Übersetzung # von stdout
Hinweis: Eine solche .mo-Datei ist eine Binärdatei, die man mit dem Kommando msgfmt aus einer Text-Datei erzeugen kann. Mit dem Kommando msgunfmt kann man wieder die Text-Form herstellen:
msgunfmt /usr/share/locale/de/LC_MESSAGES/tar.mo
Die Bash unterstützt über die Readline-Bibliothek einen Vi- und Emacs-Modus für den Kommandozeilen-Editor und den Zugriff auf die als History bezeichnete Kommando-Historie (eine Liste von früher ausgeführten Kommandos). Der Emacs-Modus ist der Standard und sollte bevorzugt verwendet werden.
Im Emacs-Mode wird vielfach die Meta-Taste benötigt. Unter Linux dient standardmäßig die linke Alt-Taste als Meta-Taste. Alternativ kann man an Stelle der Tastenkombination Meta Taste auch die Tastenfolge ESC und dann Taste eingeben.
Beispiel: Um die Tastenkombination Meta w einzugeben, kann man entweder nacheinander die Tasten ESC und w oder gleichzeitig die Tasten Alt_links und w betätigen.
Die History wird nur bei aktiver Option history gepflegt, was bei interaktiven Shells standardmäßig der Fall ist.
Setzen/Rücksetzen der History-Option:
set -o history set +o history
Die Shell verwaltet für die ausgeführten Kommandos zwei getrennte Nummern: eine Kommando-Nummer und eine History-Nummer, die aus verschiedenen Gründen differieren können. Die History-Nummer gibt die Position eines Kommandos in der History-Liste an, während die Kommando-Nummer alle von einem Shell-Prozess ausgeführten Kommandos von eins beginnend fortlaufend durchnummeriert und daher nie dekrementiert wird. Die History-Nummer kann sich dagegen verringern, wenn die History-Liste mit dem Kommando history partiell oder komplett gelöscht wird.
Die Kommando-Nummer des nächsten einzugebenden Kommandos kann man sich als Teil des Prompt-Strings PS1 unter Nutzung der Sequenz \# ausgeben lassen. Für die entsprechende History-Nummer steht die Sequenz \! zur Verfügung. Weitere Details findet man im Abschnitt Flexible Prompt-Anpassung.
Nach dem Start einer Bash bzw. nach der kompletten Löschung der History-Liste wird die History-Nummer auf den Wert eins gesetzt. Die History-Nummern der in der History gespeicherten Kommandos werden immer fortlaufend vergeben, müssen aber nicht bei eins beginnen. Der Wert der Shell-Variablen HISTSIZE legt fest, wie viele Kommandos in der History zu speichern sind. Wenn diese maximale Anzahl erreicht ist, wird vor der Aufnahme eines weiteren Kommandos das erste Kommando der History gelöscht. Dabei ändern sich aber im Gegensatz zur o.g. Löschung durch das Kommando history die History-Nummern der Kommandos nicht, weswegen sie nun nicht mehr bei eins beginnen.
Wenn HISTSIZE nicht gesetzt ist oder den leeren String als Wert hat, dann hat die History keine festgelegte maximale Größe. Ist der Wert dagegen Null, wird keine History-Liste geführt.
Da eine History-Liste auch Kommandos anderer Shells aus einer History-Datei einlesen kann, ist es möglich, dass die History-Nummer größer als die Kommando-Nummer ist.
Die Shell-Variable HISTFILE benennt eine Datei (Standard ist ~/.bash_history), in der die letzten n History-Einträge automatisch beim Beenden einer interaktiven Shell gespeichert werden. Die Zahl n entspricht dem Wert der Shell-Variablen HISTFILESIZE. Durch Löschen von HISTFILE (unset HISTFILE) kann man das Speichern der History verhindern.
Mehrzeilige Kommandos kann die Bash in verschiedener Form in die History übernehmen. Bei (mit shopt) gesetzter Option cmdhist werden mehrzeilige Kommandos geschlossen in einem History-Eintrag gespeichert, ansonsten bildet jede Zeile einen separaten History-Eintrag.
Bei aktiver Option cmdhist hängt die konkrete Speicherung der mehrzeiligen Kommandos von der (ebenfalls mit shopt einstellbaren) Option lithist ab. Ist sie gesetzt, speichert die Bash die Zeilen möglichst literal, also unter Erhaltung der eingebetteten Newline-Zeichen, so dass man beim Zugriff auf derartige History-Einträge tatsächlich ein mehrzeiliges Konstrukt im Kommandozeileneditor der Bash angeboten bekommt und bearbeiten kann (ggf. auch im externen Editor, der sich mit Ctrl-X Ctrl-E bequem starten lässt).
Andernfalls werden die Newlines geeignet durch Semikolons ersetzt, d.h., die Bash speichert das mehrzeilige Kommando in einer funktionell äquivalenten einzeiligen Form und bietet dann beim History-Zugriff auch nur diese im Kommandozeileneditor an.
Beispiel:
# mehrzeiliges Kommando for i in 1 2 3 do echo "$i" done # funktionell äquivalentes einzeiliges Kommando for i in 1 2 3; do echo "$i"; done
Anmerkung: Der Kommandozeilen-Editor der Z Shell unterstützt ebenfalls optional die mehrzeilige Darstellung. Die tcsh übernimmt generell nur die erste Zeile mehrzeiliger Kommandos in die History, so dass man nur auf diese zugreifen kann.
Mit der Shell-Variablen HISTCONTROL kann man steuern, wie Einträge in der History gespeichert werden. Diese Variable enthält eine Liste von Optionen, die durch je einen Doppelpunkt voneinander getrennt sind:
ignorespace: Zeilen, die mit einem Leerzeichen beginnen, werden nicht in die History aufgenommen.
ignoredups: Zeilen, die ihrem Vorgänger entsprechen, werden nicht in die History aufgenommen.
ignoreboth: Abkürzung für ignorespace und ignoredups
erasedups: Bevor eine Zeile in die History aufgenommen wird, werden alle früheren Einträge dieser Zeile aus der History gelöscht, um Dubletten zu vermeiden.
Unbekannte Optionen werden ignoriert. Wenn HISTCONTROL leer ist oder nur ungültige Optionen enthält, werden alle Zeilen in der History gespeichert, sofern dies laut der Shell-Variablen HISTIGNORE zulässig ist. Die Zeilen ab Zeile zwei eines mehrzeiligen zusammengesetzten Kommandos werden nicht getestet und ohne Beachtung von HISTCONTROL in die History übernommen.
Beispiel zur Einstellung von HISTCONTROL:
HISTCONTROL=ignoreboth:erasedups
Die Shell-Variable HISTIGNORE enthält eine Liste von Pfadnamens-Mustern, die auch wieder durch je einen Doppelpunkt voneinander getrennt sind. Zeilen, die auf diese Muster passen, werden nicht in die History aufgenommen. Jedes Muster wird implizit am linken Zeilenrand verankert und muss der gesamten Zeile entsprechen, wenn es wirken soll. Ein * wird also nicht implizit an das Muster angehängt.
Jedes Muster von HISTIGNORE wird gegen eine Zeile getestet, nachdem die Tests von HISTCONTROL durchgeführt wurden. Zusätzlich zu den normalen Pfadnamens-Mustern der Shell steht das Zeichen & zur Verfügung, das der vorherigen History-Zeile entspricht. Die Sonderbedeutung des Ampersands kann durch das Voranstellen eines Backslashs aufgehoben werden. Die Zeilen ab Zeile zwei eines mehrzeiligen zusammengesetzten Kommandos werden wiederum nicht getestet und ohne Beachtung von HISTIGNORE in die History übernommen.
Beispiel zur Einstellung von HISTIGNORE:
HISTIGNORE=';&:#*'
Hiermit würde man das Abspeichern von Zeilen vermeiden, die entweder aus einem Semikolon gefolgt von der Vorgänger-Zeile in der History bestehen oder mit einem Doppelkreuz beginnen (Kommentare). Von den Zeilen
echo ;echo #echo
würde dann nur die erste in die History aufgenommen werden.
Da der Doppelpunkt als Trenner zwischen den Mustern dient und im Gegensatz zum Ampersand nicht quotiert werden kann, steht er in den Mustern nicht zur Verfügung.
Tasten-Bindungen (key-bindings), d.h. Zuordnungen von Editor-Funktionen oder Makros (Strings) zu Zeichenfolgen, die über die Tastatur eingegeben werden, sind über das Kommando bind und die Datei $INPUTRC bzw. ~/.inputrc einstellbar. Diese Konfigurationsdatei wird beim Start einer Bash gelesen und dient der Initialisierung der Readline-Bibliothek. Neben Tastenbindungen kann man dort mit set auch bestimmte Readline-Variablen verändern, die das Verhalten des Kommandozeilen-Editors beeinflussen (z.B. akustische und visuelle Signale unterdrücken, mit denen im Standardfall auf Fehlersituationen hingewiesen wird).
Zur Bezeichnung von Sondertasten oder speziellen Zeichen in Tasten-Bindungen existieren Escape-Sequenzen, z.B. \M- für das Meta-Präfix und \C- für das Control-Präfix. Außerdem stehen hier die Konstrukte $if, $else und $endif zur Realisierung von Alternativen sowie $include zum Einbinden weiterer Readline-Konfigurationsdateien zur Verfügung.
Eine detaillierte Beschreibung der Readline-Konfiguration findet man im Online-Manual der Bash sowie im Info-File zu Readline, das man unter Linux normalerweise mit dem Kommando
info readline
aufrufen kann.
In einer laufenden Bash kann man Tasten-Bindungen mit dem Kommando bind manipulieren. Hier einige Beispiele:
# die Zeichenfolge ESC Leerzeichen für die History-Expandierung wie bei tcsh verwenden bind '"\M- ":history-expand-line' # Wenn man z.B. hinter dem History-Ausdruck !echo die Tastenfolge # ESC Leerzeichen eingibt, wird dieses History-Ausdruck nach Möglichkeit # durch die letzte History-Zeile ersetzt, die mit echo beginnt. # Ctrl-O führt das Kommando ls aus bind -x '"\C-o":ls' # Liste aller verfügbaren Readline-Funktionen ausgeben bind -l # alle Readline-Funktionen mit zugehörigen Tasten-Bindungen in einer wieder # einlesbaren Form ausgeben bind -p # alle Readline-Variablen mit zugehörigen Werten in einer wieder einlesbaren # Form ausgeben bind -v
Anmerkungen:
Man muss bei bind und auch in den von der Bash gelesenen Readline-Konfigurationsdateien sehr genau auf die Syntax der Angaben achten, da inkorrekte Angaben ohne Fehlermeldung ignoriert werden. So ist es z.B. wichtig, dass vor dem Doppelpunkt, der die Tastenfolge von der auszuführenden Aktion trennt, kein Leerzeichen steht.
In Experimenten hat der Autor festgestellt, dass man bestimmte Tasten-Bindungen weder mit bind noch mit einem analogen Konstrukt in der Readline-Konfigurationsdatei ändern kann. Das Handbuch sagt dazu nichts aus.
Beispiel für Zeilen aus der Datei ~/.inputrc:
# Konfiguration der Readline-Bibliothek # Readline im Emacs-Modus starten (das ist der Standard) set editing-mode emacs # keine akustischen oder visuellen Signale aussenden, um Fehler/Probleme zu # melden set bell-style none # falls bei Vervollständigungen (die typischerweise mittels Tab-Taste erfolgen) # mehr als eine Möglichkeit existiert, sollen alle Möglichkeiten immer sofort # angezeigt werden set show-all-if-ambiguous on # bei der tcsh steht standardmäßig die Tastenfolge "Escape Leerzeichen" zur # Verfügung, mit der man Bang-Ausdrücke (z.B. "!rm") expandieren lassen kann; # bei der Bash kann man dasselbe Verhalten erreichen, indem man die Tastenfolge # "Escape Leerzeichen" an die Readline-Funktion "history-expand-line" oder # "magic-space" bindet; letztere fügt nach der History-Expandierung automatisch # noch ein Leerzeichen in die Kommandozeile ein "\e ": history-expand-line #"\e ": magic-space # Tilde-Expandierung bei Wort-Vervollständigungen durchführen set expand-tilde on
Auch hier werden Kommentare durch ein # gekennzeichnet.
Die Bash unterstützt eine sehr leistungsfähige Vervollständigung von Wörtern der Kommandozeile, wodurch sich viel Tippaufwand sparen lässt und viele Tippfehler vermieden werden. Diese Vervollständigung ist kontextbezogen und programmierbar (man spricht von einer programmable completion). Zu ihrer Steuerung dient das Kommando complete.
Beispiele:
# bei bunzip2 und bzcat nur Dateinamen komplettieren, die auf .bz2 enden complete -f -X '!*.bz2' bunzip2 bzcat # -f --> Dateinamens-Vervollständigung (Alternativ-Notation: -A file) # -X --> Pfadnamens-Muster, das dem Streichen von Elementen aus der # bei der Vervollständigung erstellten Liste dient; durch das Präfix ! # wird die Wirkung negiert, d.h., die Einträge, die auf das Muster passen, # bleiben erhalten (bei uns also Elemente, die auf .bz2 enden) # bei unalias nur Alias-Namen komplettieren complete -a unalias # bei type und which nur Kommando-Namen komplettieren complete -c command type which # nutzereigene Vervollständigungs-Funktion; sie expandiert die Namen unter # /etc/sysconfig und gibt diese über das Feld COMPREPLY zurück function lsc_compl() { # COMP_WORDS: Array aller Wörter der aktuellen Kommandozeile # COMP_CWORD: Index für das Feld COMP_WORDS, der auf das Wort an der # aktuellen Cursor-Position verweist COMPREPLY=($(cd /etc/sysconfig ; echo ${COMP_WORDS[$COMP_CWORD]}*)) } # Funktion, deren Argumente durch lsc_compl komplettiert werden sollen function lsc() { for i in "$@" do ls -ld "/etc/sysconfig/$i" done } # festlegen, dass bei Kommando lsc die Vervollständigungs-Funktion lsc_compl # verwendet wird complete -F lsc_compl lsc
Das Builtin-Kommando compgen zur Generierung von Vervollständigungs-Ergebnissen bietet Unterstützung bei der Realisierung eigener Vervollständigungs-Funktionen, wie folgendes Beispiel aus dem Bash Completion Project zeigt:
# This function completes on signal names # _signals() { local i # standard signal completion is rather braindead, so we need # to hack around to get what we want here, which is to # complete on a dash, followed by the signal name minus # the SIG prefix COMPREPLY=( $( compgen -A signal SIG${cur#-} )) for (( i=0; i < ${#COMPREPLY[@]}; i++ )); do COMPREPLY[i]=-${COMPREPLY[i]#SIG} done } # nutzen ließe sich diese Funktion für das Kommando kill z.B. so: function sig_helper() { local cur=${COMP_WORDS[COMP_CWORD]} _signals } complete -F sig_helper kill # Anmerkung: Im Bash Completion Project erfolgt die Nutzung von _signals # etwas anders, um dem Nutzer noch mehr Komfort zu bieten.
Der Funktions-Umfang von Readline ist sehr groß. In der Praxis dürfte eine sinnvolle Auswahl genügen, die man sich auch merken kann. Die folgende Tabelle enthält ausgewählte Tastenkombinationen bei Nutzung der standardmäßig aktiven Tasten-Bindungen im Emacs-Modus:
Cursortasten links/rechts | Cursor ein Zeichen nach links/rechts positionieren |
Cursortasten auf/ab | History rückwärts/vorwärts durchlaufen |
TAB (Tabulatortaste) | "intelligente" Vervollständigung des aktuellen Arguments links vom Cursor |
Ctrl-X * | Expandierung des Pfadnamens-Musters links vom Cursor |
Meta-^ | History-Expandierung (für mit ! beginnende Ausdrücke) |
ENTER oder Ctrl-M | Übergabe der Kommandozeile zur Auswertung an die Shell |
Pos1 oder Ctrl-A | Cursor an den Zeilenanfang positionieren |
Ende oder Ctrl-E | Cursor ans Zeilenende positionieren |
Meta-b | Cursor nach links bis zum nächsten Wortanfang positionieren |
Meta-f | Cursor nach rechts bis hinter das nächste Wortende positionieren |
Meta-u | alle Zeichen von der Cursor-Position bis zum nächsten Wortende nach rechts in Großbuchstaben umwandeln |
Meta-l | alle Zeichen von der Cursor-Position bis zum nächsten Wortende nach rechts in Kleinbuchstaben umwandeln |
Backspace oder Ctrl-H | Zeichen links vom Cursor löschen |
Ctrl-D | Zeichen unter dem Cursor löschen |
Ctrl-K | ab Cursor bis Zeilenende löschen |
Ctrl-W | nach links bis zum nächsten Wortanfang löschen |
Ctrl-X Backspace oder Ctrl-U | nach links bis zum Zeilenanfang löschen |
Meta-d | nach rechts bis zum nächsten Wortende löschen |
Ctrl-V | das Folgezeichen literal in die Kommandozeile einfügen
Beispiel: Durch Ctrl-V Ctrl-M wird das Zeichen Ctrl-M (Wagenrücklauf) in die Kommandozeile eingefügt. Ctrl-M ohne Präfix Ctrl-V entspricht der ENTER-Taste und würde die Kommandozeile zur Auswertung an die Shell übergeben. |
Interrupt-Zeichen |
Kommandozeile abbrechen
Das Interrupt-Zeichen lautet typischerweise Ctrl-C. Es kann mit dem Kommando stty eingestellt werden und bewirkt das Senden des Signals SIGINT. |
Ctrl-L | Bildschirm löschen und aktuelle Kommandozeile an den oberen Bildschirm-Rand verschieben |
Meta-< | Sprung zum Anfang der History |
Meta-> | Sprung zum Ende der History |
Ctrl-R | inkrementelle History-Suche rückwärts |
Ctrl-S | inkrementelle History-Suche vorwärts
Achtung: Oft ist Ctrl-S das Stopp-Zeichen für die Terminal-Flusskontrolle und wirkt dann auch so (Start der gestoppten Ausgabe mit Ctrl-Q). Diese Einstellung kann mit dem Kommando stty geändert werden, z.B. folgendermaßen auf Ctrl-G: stty stop '^G' |
Meta-. | das letzte Wort der letzten Zeile einfügen |
Meta-n Meta-. | das n-te Wort der letzten Zeile einfügen |
Meta-Ctrl-E |
die aktuelle Zeile expandieren, wie es die Bash tut
Zuerst werden eine Alias- und History-Expandierung realisiert. Danach folgen alle Wort-Expandierungen der Shell (also alle Expandierungen mit Ausnahme der Klammern-, Tilde- und Pfadnamens-Expandierungen). |
Ctrl-X Ctrl-E |
die aktuelle Kommandozeile im externen Editor öffnen und das Resultat ausführen
Die Bash versucht der Reihe nach, die Editoren $FCEDIT, $EDITOR und emacs zu starten. |
Ctrl-X Ctrl-V | Versionsinformationen zur aktuellen Bash-Instanz ausgeben |
Für den Zugriff auf die History stehen neben den recht umfangreichen Möglichkeiten der Readline-Bibliothek auch Kommando-Konstrukte zur Verfügung, die von der tcsh übernommen wurden. Sie werden standardmäßig mit einem auch als Bang bezeichneten Ausrufezeichen (!) eingeleitet und haben allgemein folgendes Format:
!Ereignisbezeichner[:Wortbezeichner[:Modifikatoren]]
Anmerkung: Möchte man statt des Ausrufezeichens ein anderes Zeichen verwenden, so muss man dieses als erstes Zeichen der Shell-Variablen histchars eintragen. Davon ist aber abzuraten. Wir gehen nachfolgend davon aus, dass immer das Ausrufezeichen verwendet wird.
Diese History-Substitution bzw. -Expandierung steht nur dann zur Verfügung, wenn die Option histexpand bzw. H gesetzt ist, was bei interaktiven Shells standardmäßig der Fall ist. Diese Option kann folgendermaßen gesetzt/rückgesetzt werden:
set -H set -o histexpand set +H set +o histexpand
Hinweis: Diese zur History-Expandierung genutzten Konstrukte werden nicht in der ursprünglichen, sondern in expandierter Form in die History übernommen.
Beispiel:
echo eins zwei drei echo !$
Der History-Ausdruck !$ wird durch das letzte Wort des vorigen Kommandos ersetzt. In die History wird daher die Zeile
echo drei
und nicht
echo !$
aufgenommen.
Falls dem Ausrufezeichen eines der folgenden Zeichen folgt, wird die History-Expandierung verhindert:
Achtung: Die History-Expandierung mittels ! kann nur durch Backslash und einfache Apostrophe, nicht aber durch Doppelapostrophe aufgehoben werden.
Beispiel: Die beiden Kommandos
: eins zwei drei echo \!$ '!$' "!$"
erzeugen die Ausgabe
!$ !$ drei
Die Bash unterstützt mehrere Ereignisbezeichner (event designators), mit denen man auf Zeilen der History Bezug nehmen kann:
!n | Kommandozeile n |
!-n | aktuelle Kommandozeile minus n |
!! | voriges Kommando (Abkürzung für !-1) |
!string | letzte Kommandozeile, die mit string beginnt |
!?string? | letzte Kommandozeile, die string enthält
Das ? am Ende kann entfallen, wenn hinter string ein Newline-Zeichen (also das Zeilenende) folgt. |
^string1^string2^ |
Schnell-Substitution: Wiederholung des letzten Kommandos, wobei
string1 durch string2 ersetzt wird
Diese Substitution ist eine Kurzform für !!:s/string1/string2/(s. unten bei Modifikatoren). Sie wirkt nur, wenn sie unmittelbar am Anfang einer Kommandozeile steht. Es darf also auch kein Leerzeichen vorausgehen. Das abschließende ^ kann entfallen, wenn hinter dem Ausdruck ein Newline-Zeichen folgt. Anmerkung: Möchte man statt des Hütchens (^) ein anderes Zeichen verwenden, so muss man dieses als zweites Zeichen der Shell-Variablen histchars eintragen. Davon ist aber abzuraten. |
!# | die komplette bisher geschriebene Kommandozeile |
Wenn der Ereignisbezeichner hinter dem Ausrufezeichen fehlt, bezieht sich das History-Konstrukt immer auf die letzte Zeile der History.
Auf die einzelnen Wörter einer History-Zeile kann man über optionale Wortbezeichner (word designators) zugreifen. Fehlt der Wortbezeichner, so bezieht sich das History-Konstrukt auf die gesamte durch den Ereignisbezeichner gewählte History-Zeile.
Standardmäßig wird ein Ereignisbezeichner vom Wortbezeichner durch einen Doppelpunkt (:) getrennt. Der Doppelpunkt kann entfallen, wenn der Wortbezeichner mit einem der Zeichen
^ $ * - %
beginnt.
Die Wörter der History-Zeilen werden mit Null beginnend von links nach rechts durchnummeriert. Wörter werden im Rahmen der History-Expandierungen in die aktuelle Kommandozeile jeweils so eingefügt, dass sie durch einzelne Leerzeichen voneinander getrennt sind.
Die folgende Tabelle listet die verfügbaren Wortbezeichner auf:
0 | nulltes Wort einer Zeile (Kommando-Wort) |
n | n-tes Wort der Zeile |
^ | erstes Argument, also Wort 1 |
$ | letztes Argument |
% | Wort, das bei der letzten String-Suche über ?string? passte (gefunden wurde) |
x-y | Wortbereich von Wort x bis Wort y; -y entspricht 0-y |
* | alle Wörter mit Ausnahme des nullten; identisch mit 1-$ |
x* | Abkürzung für x-$ |
x- | Abkürzung für x-$, wobei aber das letzte Wort ausgelassen wird (also Wort x bis zum vorletzten Wort) |
Hinter einem Wortbezeichner kann eine Folge von Modifikatoren (modifiers) stehen, wobei jeder Modifikator in der Regel durch einen Doppelpunkt eingeleitet wird:
h | head: entfernt die letzte Pfadnamenskomponente, also den Dateinamen |
t | tail: entfernt alle führenden Pfadnamenskomponenten und erhält den Dateinamen |
r | entfernt das abschließende Suffix der Form .xxx und belässt den Basisnamen |
e | extension: entfernt alles bis auf das abschließende Suffix (die letzte Dateinamens-Erweiterung) |
p | print: Ausgabe des neuen Kommandos, ohne es auszuführen |
q | quote: Alle substituierten Wörter werden geschlossen durch einfache Apostrophe quotiert, um weitere Substitutionen zu verhindern. |
x | extended quote: Die substituierten Wörter werden einzeln durch einfache Apostrophe quotiert, um weitere Substitutionen zu verhindern. |
s/alt/neu/ | substitution: Das erste Auftreten der Zeichenfolge alt wird durch neu ersetzt. Statt / können auch beliebige andere Trenner verwendet werden. Der abschließende Trenner ist optional, wenn danach das Zeilenende folgt. Wenn der Trenner in alt oder neu vorkommt, ist er durch einen Backslash zu quotieren. Wenn ein Ampersand (&) in neu vorkommt, wird es durch alt ersetzt. Ein Backslash hebt diese Sonderbedeutung von Ampersand auf. Wenn alt der leeren Zeichenfolge entspricht, so wird statt dessen der Wert von alt aus der letzten History-Substitution verwendet. Wenn es keine solche gab, so wird der letzte Such-String aus dem Ereignisbezeichner der Form !?string[?] verwendet. |
& | wiederholt die letzte Substitution |
g | global: Bewirkt, dass die Substitutionen der Modifikatoren
s oder & global erfolgen, also jedes
Auftreten von alt durch neu ersetzt wird.
Beispiel:
:gs/alt/neu/
Statt g kann auch der Modifikator a verwendet werden. |
G | Global: Der folgende Modifikator s oder & wird genau einmal auf jedes Wort der History-Zeile angewendet. |
Hinweis: Die Modifikatoren s und & folgen den Modifikatoren g, a und G also unmittelbar und nicht nach einem Doppelpunkt.
Zur Anzeige und Manipulation der History-Liste dienen die eingebauten Kommandos history und fc (fix command), deren Anwendung nachfolgend an einigen Beispielen demonstriert wird.
Mit fc kann man Teile der History in einen Editor laden lassen und dort modifizieren. Nach dem Speichern des Modifikations-Ergebnisses wird dieses von der Bash ausgegeben und ausgeführt. Falls man nichts ausführen möchte, weil man z.B. die falschen Kommandos in den Editor geladen hat, sollte man alle geladenen Kommandos im Editor löschen und dann die leere Datei abspeichern.
Den zu verwendenden Editor kann man mit der Option -e angeben, z.B.:
fc -e pico
Ohne diese Option verwendet die Bash den Editor, der als Wert der Shell-Variablen FCEDIT festgelegt wurde. Existiert diese Variable nicht, wird der Wert der Shell-Variablen EDITOR verwendet. Falls auch diese nicht existiert, wird der Editor vi gestartet.
Das Kommamndo fc kennt zwei Aufruf-Formen:
fc [-e Editor] [-nlr] [Anfang] [Ende] fc -s [Muster=Ersatz] [Kommando]
Bei Form 1 wird der durch Anfang und Ende begrenzte Kommando-Bereich spezifiziert. Die beiden Bereichsgrenzen können als Strings oder als Zahlen angegeben werden. Strings selektieren das letzte Kommando, das mit diesem String beginnt. Zahlen werden als History-Nummer gewertet, wobei die Bash negative Zahlen als Offset zur aktuellen History-Nummer interpretiert.
Wenn die Angabe Ende fehlt, so wird sie bei der History-Anzeige auf die aktuelle History-Nummer und bei der History-Modifikation auf den Wert Anfang gesetzt. Folglich werden bei der Anzeige die Kommandos von Anfang bis zum Ende der Liste ausgegeben. Bei der Modifikation wird genau das Kommando Anfang ausgewählt.
Wenn auch die Angabe Anfang fehlt, wird bei der Modifikation das letzte Kommando der History gewählt und bei der Anzeige der Wert Anfang auf -16 gesetzt, so dass maximal die letzten 16 Kommandos angezeigt werden.
Die Option -n unterdrückt die Ausgabe der History-Nummern. Die Option -r kehrt die Ordnung der Kommandos um, d.h., sie werden in umgekehrter Reihenfolge ausgegeben bzw. in den Editor geladen. Wenn die Option -l aktiv ist, werden die History-Kommandos auf die Standard-Ausgabe ausgegeben, ansonsten wie oben beschrieben in den Editor geladen.
Bei Form 2 wird das gewählte Kommando erneut ausgeführt, nachdem alle Vorkommen von Muster durch Ersatz ersetzt wurden. Für die Kommando-Auswahl stehen wieder die oben beschriebenen Zahlen oder Strings zur Verfügung. Fehlt Kommando, so wird das letzte Kommando der History selektiert.
Kommando-Beispiele zur Arbeit mit der History:
# komplette Anzeige der History history # Vor jedem Kommando wird bei history die History-Nummer angezeigt. # Wenn die Shell-Variable HISTTIMEFORMAT gesetzt ist, wird ab Bash 3.0 # hinter der History-Nummer auch noch ein Zeitstempel ausgegeben. # Anmerkung: # Geänderte History-Einträge werden durch einen Stern (*) vor dem # Kommando und hinter einer evtl. angezeigten History-Nummer # gekennzeichnet. Die Änderung eines History-Eintrags (und damit das Setzen # des Sterns) kann man erreichen, indem man z.B. durch Betätigung der # Cursor-Tasten auf/ab einen bestimmten Eintrag in den # Kommandozeilen-Editor lädt, diesen dort modifiziert und durch eine # nachfolgende erneute Betätigung der Cursor-Taste auf oder ab in die History # zurückspeichert, ohne ihn auszuführen. # Eine Beendigung der Modifikation durch ENTER erzeugt keinen Stern, da ja # hierbei der modifizierte Eintrag ausgeführt und dadurch in der Regel als # neuer Eintrag hinten an die History-Liste angefügt wird. fc -l # Anzeige mit History-Nummern fc -ln # Anzeige ohne History-Nummern # Im Unterschied zu history gibt fc generell keinen Zeitstempel aus. # Anzeige der letzten 7 History-Kommandos history 7 fc -l -7 # Anzeige der History-Kommandos 7 bis 9 fc -l 7 9 # Anzeige des vorvorletzten und vorletzten History-Kommandos fc -l -3 -2 # Suche in der History mittels grep history | grep ls # Anzeige aller Kommandos, die ls enthalten # Korrektur von Kommandos mit dem Editor fc # letztes Kommando der History-Liste fc 1 # Kommando 1 fc 5 10 # Kommandos 5 bis 10 fc 5 echo # Kommandos 5 bis zum letzten Kommando, das mit echo beginnt fc -r 7 x # Kommandos 7 bis zum letzten Kommando, das mit x beginnt, # wobei diese in umgekehrter Reihenfolge in den Editor geladen werden fc -1 # letztes Kommando fc -2 # vorletztes Kommando # Kommando 8 wiederholen und jedes x durch a ersetzen fc -s x=a 8 # letztes mit man beginnendes Kommando wiederholen und jedes ls # durch echo ersetzen fc -s ls=echo man # History komplett löschen history -c # Kommando 5 der History löschen history -d 5 # aktuelle History durch den Inhalt der History-Datei ~/history ersetzen history -r ~/history # aktuelle History in die History-Datei ~/history sichern history -w ~/history # letzte Zeile nochmal ausführen !! # letztes Kommando, das mit h beginnt, nochmal ausführen !h # !$ steht für das letzte Wort des letzten Kommandos wc -l !$ # Alternativ könnte man auch notieren: wc -l !:$ wc -l !!$ wc -l !!:$ # Schnell-Substitution: ls -l /etc/passwd # letztes Kommando nochmal ausführen, aber -l durch nichts ersetzen, # also löschen ^-l^ # Ergebnis der Substitution: ls /etc/passwd # letztes Kommando nochmal ausführen, aber -l durch -i ersetzen ^-l^-i # Ergebnis der Substitution: ls -i /etc/passwd # Falls man an das Ergebnis der obigen Schnell-Substitution noch etwas anhängen # möchte, benötigt man das abschließende ^. Im folgenden Beispiel wird # /tmp/ angehängt: ^-l^-i^ /tmp # Ergebnis der Substitution: ls -i /etc/passwd /tmp # Ohne das abschließende ^ sähe es so aus: ^-l^-i /tmp # Ergebnis der Substitution: ls -i /tmp /etc/passwd # Hier wird -l durch -i /tmp ersetzt. # Über die Shell-Variable HISTTIMEFORMAT ist ab Bash 3.0 die Einstellung des # Formats des Zeitstempels möglich, der bei history ausgegeben wird: HISTTIMEFORMAT='(%d.%m.%Y) %H:%M ' # In Klammern wird hier das Datum und dahinter die Uhrzeit angegeben, # z.B. (08.01.2006) 19:47 # Unterschied zwischen den Modifikatoren g und G : 1 2 3453 1 2 3453 !*:gs/3/9/:p # expandiert zu 1 2 9459 1 2 9459 # # Alle Vorkommen der 3 werden durch 9 ersetzt. # # Erläuterung des Konstrukts: Da der Ereignisbezeichner fehlt, bezieht sich # der History-Ausdruck auf die vorherige Zeile (die letzte Zeile der # History). Als Wortbezeichner dient der *, der alle Argumente # auswählt. Er muss vom Ereignisbezeichner nicht durch einen Doppelpunkt # getrennt werden. # Hinter dem Wortbezeichner folgen die beiden Modifikatoren g und # s, die nicht durch Doppelpunkt getrennt werden dürfen. Sie # realisieren eine globale Substitution von 3 durch 9. # Der anschließend folgende Modifikator :p bewirkt, dass das Ergebnis # der History-Substitution nur in die Kommandozeile übernommen (also # angezeigt) wird, ohne es der Shell zur Auswertung zu übergeben. : 1 2 3453 1 2 3453 !*:Gs/3/9/:p # expandiert zu 1 2 9453 1 2 9453 # # Im Unterschied zum obigen Konstrukt wird hier das jeweils erste # Vorkommen der 3 in einem Wort durch 9 ersetzt.
Ein Alias bzw. Alias-Name ist ein Kurz- oder Alternativ-Name für ein anderes (meist längeres oder schwer zu merkendes) Shell-Konstrukt. Er wird speziell in interaktiven Shells benutzt, um den Tippaufwand zu reduzieren oder sich leichter merkbare Kommandos zu schaffen.
Im Rahmen einer Alias-Definition wird dem Alias-Namen ein Ersetzungstext zugewiesen. Dazu dient das eingebaute Kommando alias:
alias Aliasname=Ersetzungstext [Aliasname=Ersetzungstext ...]
Einem bereits existierenden Alias-Namen kann man durch eine solche Alias-Definition jederzeit einen anderen Ersetzungstext zuweisen.
Mit dem ebenfalls eingebauten Kommando unalias lässt sich eine Alias-Definition wieder aufheben:
unalias Aliasname [Aliasname ...]
Mit dem Kommando alias kann man sich auch alle Alias-Definitionen oder eine ausgewählte Alias-Definition auflisten lassen:
# alle: alias # ausgewählte: alias Aliasname [Aliasname ...]
Der Ersetzungstext unterliegt den typischen Expandierungen (z.B. Variablen-, Kommando- und arithmetische Substitution) und muss aus Sicht der Shell ein einzelnes Wort sein. Ggf. ist also bei der Alias-Definition eine geeignete Quotierung nötig, z.B.:
alias rm='rm -i' alias xyz="echo Alias-Definition am $(date) durch $USER \; 3+4=$((3+4))"
Anmerkung: Das Semikolon im Alias xyz wurde gesondert quotiert, da es sonst als Trenner zwischen zwei Kommandos interpretiert wird. Alternativ hätte man z.B. den Alias auch unter Verwendung einfacher Apostrophe als Quotierungs-Zeichen definieren können:
alias xyz="echo 'Alias-Definition am $(date) durch $USER ; 3+4=$((3+4))'"
Im Rahmen der Auswertung von Kommandozeilen führt die Bash bei Bedarf eine Alias-Expandierung durch. D.h., sie ersetzt das erste Wort eines einfachen Kommandos, das einem Alias-Namen entspricht, durch den Ersetzungstext, sofern dieses Wort nicht quotiert und nicht das Ergebnis einer anderen Expandierung (z.B. Parameter-Expandierung) ist.
Eine Alias-Expandierung findet in der Bash allerdings nur dann statt, wenn die Option expand_aliases gesetzt ist. Bei interaktiven Shells ist dies standardmäßig der Fall. Bei nicht-interaktiven Shells muss die Option explizit gesetzt werden:
shopt -s expand_aliases
Als Ersetzungstext ist ein beliebiges (gültiges) Shell-Konstrukt möglich, das von der Shell so ausgewertet wird, als hätte es an Stelle des Alias-Namens in der Kommandozeile gestanden. Im Gegensatz zum Ersetzungstext dürfen im Alias-Namen keine Shell-Metazeichen, keine Quotierungs-Zeichen und keines der Zeichen
/ $ ` =
verwendet werden.
Wenn das erste Wort des Ersetzungstextes ein gültiger Alias-Name ist, der nicht dem gerade zu expandierenden Alias-Namen entspricht, erfolgt eine weitere Alias-Expandierung. Bei identischen Namen unterbleibt diese weitere Alias-Expandierung, da die Bash generell rekursive Alias-Expandierungen unterbindet. Das gilt z.B. auch für Konstellationen folgender Art:
alias xx=yy alias yy=xx
Wenn der Ersetzungstext mit einem Leerzeichen endet, dann unterliegt auch das Wort, das einem Alias-Namen folgt, einer Alias-Expandierung, wie folgendes Beispiel zeigt:
alias m='mkdir '
alias p='-p '
alias v=-v
m p v foo/bar
# ausgeführt wird:
# mkdir -p -v foo/bar
Bash-Aliase unterstützen im Gegensatz zu Shell-Funktionen (und tcsh-Aliasen) keine Argumente.
Zur Vermeidung ungewollter Effekte sollten Alias-Definitionen immer auf einer separaten Zeile und außerhalb zusammengesetzter Kommandos und damit auch außerhalb von Shell-Funktionen erfolgen. Alias-Definitionen innerhalb einer Shell-Funktion sind erst wirksam, nachdem die Funktion das erste Mal ausgeführt wurde.
Aliase, die innerhalb eines Funktionskörpers genutzt werden, werden expandiert, wenn die Shell die Funktionsdefinition auswertet, und nicht, wenn die Funktion ausgeführt wird. Hier ein Beispiel:
alias rm='rm -i' function fkt() { rm /tmp/xxx }
Im Funktionskörper wird rm durch rm -i ersetzt. Die Bash speichert sich also folgende Funktionsdefinition, die man sich durch das Kommando
declare -f fkt
anzeigen lassen kann:
fkt () { rm -i /tmp/xxx }
Zum Zeitpunkt der Ausführung dieser Funktion wird also der Alias rm nicht mehr benötigt. Man kann ihn bis dahin löschen oder anders definieren:
unalias rm fkt
Der Aufruf der Funktion fkt bewirkt trotzdem die Ausführung des Kommandos
rm -i /tmp/xxx
Funktionen sind allgemein flexibler als Alias-Konstrukte. Es empfiehlt sich daher, möglichst Funktionen statt Alias-Definitionen zu verwenden.
Wietere Beispiele:
# Alias definieren alias l='ls -l' # alle Alias-Definitionen auflisten lassen alias # Definition des Alias l auflisten lassen alias l # Alias nutzen l /tmp # wird zu ls -l /tmp expandiert # Alias-Definition aufheben unalias l l /tmp # rm als weiteren Alias definieren alias rm='rm -i' # Nutzung des Alias rm rm file # durch Quotierung kann man die Alias-Expandierung unterbinden \rm file 'rm' file "rm" file # hier wird also die Shell-Funktion rm (sofern definiert) bzw. # das Standard-Kommando rm ausgeführt # tcsh-Alias mit Argument und äquivalente Bash-Funktion # # Ziel: Alias bzw. Funktion psg definieren, die die # Prozess-Status-Liste mittels AWK nach einem Muster durchsucht und # dabei das Such-Kommando selbst ausblendet # # Aufruf-Beispiel: psg 'kmix.*session' # sucht nach einem Prozess-Eintrag, in dem irgendwo der String # kmix und weiter hinten der String session vorkommt # Realisierung als tcsh-Alias: # # alias psg 'eval set muster=\!:1:q ; ps auxww | sed 1d | awk "/$muster/" | fgrep -v "awk /$muster/"' # # Durch das History-Konstrukt \!:1:q greift der Alias auf das erste Argument # hinter dem Alias-Namen zu, im obigen Beispiel also auf kmix.*session. # Realisierung einer funktionell äquivalenten Bash-Funktion: function psg() { ps auxww | sed 1d | awk "/$1/" | fgrep -v "awk /$1/" } # Mit $1 greift die Bash-Funktion auf das erste Argument # hinter dem Funktionsnamen zu, im obigen Beispiel also auf kmix.*session. # Anmerkung: # In der tcsh stehen nur Aliase und keine Funktionen zur Verfügung. # Das ist ein Nachteil der tcsh gegenüber den Shells der # Bourne-Shell-Familie, da Funktionen allgemein eine höhere Flexibilität # als Aliase bieten, unabhängig davon, ob die Aliase Argumente akzeptieren # oder nicht. # Alias-Expandierung erfolgt nicht rekursiv: alias ls='ls -F' ls # Expandierung zu ls -F # Das ls im Ersetzungstext wird nicht nochmal expandiert. # wiederholte Alias-Expandierung alias a1='echo alias a1' alias a2='echo alias a2' alias a3='echo alias a3 ' # mit Leerzeichen am Ende a1 a2 # Expandierung zu echo alias a1 a2 a3 a2 # Expandierung zu echo alias a3 echo alias a2
Wenn der Bash eine Kommandozeile zur Ausführung übergeben wird, führt sie bei Bedarf (d.h. beim Auftreten nicht quotierter Zeichen mit Sonderbedeutung) eine Reihe von Expandierungen (Substitutionen) durch, bevor sie die Zeile wirklich ausführt. Expandierungen können prinzipiell alle Wörter bzw. Stellen der Kommandozeile betreffen. Sie sind also nicht auf Kommandonamen oder Argumente beschränkt. Durch eine geeignete Quotierung mit Backslash (\), Apostroph (') und Doppelapostroph (") kann man Expandierungen komplett oder teilweise unterdrücken.
Expandierungen erfolgen in einer festen Reihenfolge. Die folgende geordnete Liste nennt die einzelnen Schritte, die bei der Auswertung einer Kommandozeile durchlaufen werden:
History-Expandierungen
Zerlegung der Kommandozeile in einzelne Wörter (Token), wobei die unter Terminologie genannten Metazeichen als Trenner dienen
Alias-Expandierungen
Expandierung geschweifter Klammern (brace expansion)
Tilde-Expandierung (tilde expansion)
von links nach rechts in der Zeile:
Diese vier Expandierungen erfolgen also nicht nacheinander, sondern gewissermaßen simultan. Die Zeile wird daher nicht vier Mal, sondern nur einmal von links nach rechts durchlaufen, um alle vier genannten Expandierungen zu realisieren.
Aufspaltung in Wörter (word splitting)
Pfadnamens-Expandierung (pathname expansion)
Entfernung der Quotierungs-Zeichen (quote removal)
Die Klammern-, Tilde- und Pfadnamens-Expandierungen können die Anzahl der Wörter einer Zeile verändern. Alle anderen Expandierungen substituieren normalerweise ein Wort durch ein anderes Wort. Einzige Ausnahme sind die Expandierungen "$@" (Liste der Positionsparameter) und "${feld[@]}" (Liste aller Feld-Elemente).
Aus der festen Reihenfolge der Expandierungen resultiert, dass man ggf. eine weitere Auswertungs-Runde mittels eval erzwingen muss:
# der Reihe nach in die Home-Verzeichnisse der Nutzer egon, berta und anton # wechseln (um dort bestimmte Kommandos auszuführen) for user in egon berta anton do eval cd ~$user # Da die Tilde-Expandierung vor der Parameter-Expandierung erfolgt, führt # das (bei tcsh, ksh und zsh auf Grund einer anderen Expandierungs- # Reihenfolge wie gewünscht funktionierende) Kommando # # cd ~$user # # bei der Bash dazu, dass an das Kommando cd ein Argument der Form ~egon # übergeben wird, was einen Fehler zur Folge hat. Um den Ausdruck ~egon # durch den absoluten Pfad zum Home-Verzeichnis von Nutzer egon ersetzen # zu lassen, muss eine weitere Auswertung durch das Kommando eval # erzwungen werden. # hier die gewünschten Kommandos ausführen ... done
Wenn bei einfachen Kommandos der Kommando-Name einem nicht quotierten Alias-Namen entspricht, erfolgt die weiter oben beschriebene Alias-Expandierung. Entsteht der Name eines einfachen Kommandos erst im Ergebnis einer Expandierung, erfolgt standardmäßig keine Alias-Expandierung. Um sie auch hier zu erreichen, benötigt man wieder das Kommando eval:
# Alias al definieren alias al='echo Alias' # den Alias-Namen al an die Variable cmd zuweisen cmd=al # Kommando ausführen, dessen Name in cmd steht $cmd # Die Bash expandiert hier $cmd zu al. Dieses Kommando existiert nicht. # Daher bekommt man eine Fehlermeldung: bash: al: command not found # Mit eval wird der Alias expandiert, so dass echo Alias ausgeführt wird: eval $cmd
Nachfolgend sollen die verschiedener Expandierungen detailliert dargestellt und an Beispielen erläutert werden, wobei wir auf History- und Alias-Expandierungen nicht eingehen, weil diese an anderer Stelle ausführlich besprochen werden. Zur leichteren Orientierung in dem relativ langen Text werden Syntax-Angaben rot hervorgehoben:
Expandierung geschweifter Klammern (Brace-Expandierung)
Sie steht nur dann zur Verfügung, wenn die set-Option braceexpand bzw. B aktiv ist, was standardmäßig der Fall ist. Die Brace-Expandierung dient der Generierung beliebiger Strings und ähnelt der Pfadnamens-Expandierung, allerdings müssen die generierten Strings nicht als Dateinamen existieren, da die Expandierung rein textuell erfolgt und weder ihr Kontext noch das zu expandierende Muster einer inhaltlichen Interpretation unterliegt.
Die Syntax für ein Muster der Brace-Expandierung (Brace-Muster) sieht so aus:
[Präfix]{geklammertes Muster}[Suffix]
Präfix und Suffix sind optional. Die geschweiften Klammern dürfen nicht quotiert sein. Bei dem geklammerten Muster muss es sich entweder
um eine mindestens zweielementige Folge von Strings, die durch je ein unquotiertes Komma voneinander getrennt sind, oder
einen Sequenz-Ausdruck der Form x..y
handeln. Die Strings einer Folge können auch leer sein.
Bei der Brace-Expandierung wird zunächst das geklammerte Muster expandiert. Als Ergebnis entsteht eine durch Leerzeichen getrennte String-Liste. Jedes Element dieser Liste erhält anschließend das Präfix bzw. das Suffix des Brace-Musters als sein eigenes Präfix bzw. Suffix. Die Reihenfolge der Listen-Elemente bleibt dabei erhalten.
Brace-Muster sind schachtelbar. Wenn ein geklammertes Muster ein weiteres Brace-Muster enthält, wird im Rahmen der Expandierung des äußeren Musters zunächst das innere Muster expandiert und in expandierter Form weiterverwendet.
Eine Folge von durch je ein Komma getrennten Strings innerhalb des geklammerten Konstrukts repräsentiert die Liste der aufgezählten Strings in der angegebenen Reihenfolge, es sei denn, es handelt sich bei der String-Folge um ein gültiges Brace-Muster. Dann wird dieses gesamte innere Muster geschlossen expandiert.
Sequenz-Ausdrücke stehen erst ab Bash 3.0 zur Verfügung. Sie erzeugen entweder eine numerisch geordnete Sequenz ganzer Zahlen oder eine lexikografisch geordnete Zeichen-Sequenz. Die Werte x und y werden als Start- und End-Wert der Sequenz interpretiert. Sie müssen entweder beide eine ganze Zahl oder beide ein Zeichen sein. Wenn der Start-Wert kleiner als der End-Wert ist, entsteht eine aufsteigend sortierte Sequenz. Sind beide Werte identisch, hat die Sequenz genau ein Element, nämlich x. Andernfalls entsteht eine fallend sortierte Sequenz.
Syntaktisch inkorrekte Brace-Muster bleiben unverändert erhalten. Die Expandierung eines Brace-Musters kann man gezielt verhindern, wenn man mindestens eines der Zeichen quotiert, an denen die Bash ein Brace-Muster erkennt, also z.B. eine der geschweiften Klammern, das Komma einer String-Folge oder einen Punkt eines Sequenz-Ausdrucks.
Um Konflikte mit der Parameter-Expandierung zu vermeiden, wird die Zeichenfolge ${ bei der Brace-Expandierung ignoriert, d.h., die öffnende geschweifte Klammer hinter dem Dollarzeichen wird nicht als Beginn eines geklammerten Musters gewertet.
Beispiele:
# geklammertes Muster enthält eine String-Folge: /etc/{passwd,shadow} # expandiert zu /etc/passwd /etc/shadow /etc1/a{passwd1,shadow1} # expandiert zu /etc1/apasswd1 /etc1/ashadow1 a{11,12}b # expandiert zu a11b a12b a{11,}b # expandiert zu a11b ab a{,11}b # expandiert zu ab a11b a{,}b # expandiert zu ab ab # geklammertes Muster enthält einen Sequenz-Ausdruck: a{a..c}b # expandiert zu aab abb acb a{c..a}b # expandiert zu acb abb aab a{a..a}b # expandiert zu aab a{11..13}b # expandiert zu a11b a12b a13b a{11..11}b # expandiert zu a11b a{-1..-3}b # expandiert zu a-1b a-2b a-3b # geschachtelte Brace-Expandierung: f{a{b,c,d}e}g # expandiert zu fabeg faceg fadeg # Das geklammerte Muster des äußeren Brace-Musters ist hier wiederum ein # Brace-Muster. Es wird zur Folge abe ace ade expandiert. # Jedes Element dieser Folge erhält anschließend das Präfix f und das # Suffix g des äußeren Brace-Musters. Die Brace-Expandierung erfolgt, # weil es sich rein formal gesehen bei dem geklammerten Muster um eine Folge # von durch Kommas getrennten Strings handelt, die allerdings hier nicht die # Liste der aufgezählten Strings repräsentiert. # Wenn das geklammerte Muster einen Brace-Ausdruck ohne Komma darstellt, dann # unterbleibt die Brace-Expandierung: a{b{1..2}c}d # Hier bleibt das Brace-Muster a{b{1..2}c}d unverändert erhalten, da es sich # beim geklammerten Muster b{1..2}c weder um durch je ein Komma # voneinander getrennte Strings noch um einen Sequenz-Ausdruck handelt und # somit kein gültiges Brace-Muster vorliegt. # Wenn das Komma allerdings vorkommt, wird ein Sequenz-Ausdruck im inneren # Brace-Muster normal mit expandiert: a{b,c{1..2},d}e # expandiert zu abe ac1e ac2e ade # weitere inkorrekte Brace-Muster, die unverändert erhalten bleiben: a{11}b # 11 ist weder String-Folge noch Sequenz-Ausdruck a{1..a}b # 1..a ist kein gültiger Sequenz-Ausdruck, # da vorn eine Zahl und hinten ein Zeichen steht # ${ wird bei der Brace-Expandierung ignoriert. # Wir nehmen hier an, dass die Variable a den Wert 111 hat: x${a/1/,b}y # expandiert zu x,b11y, da der Ausdruck ${a/1/,b} # eine Variablen-Expandierung veranlasst: Inhalt der Variablen a, # beim das erste Auftreten einer 1 durch ,b ersetzt wird # durch Quotierung des Dollarzeichens erfolgt die Brace- statt der # Variablen-Expandierung: x\${a/1/,b}y # expandiert zu x$a/1/y x$by
Wenn ein Wort mit einer unquotierten Tilde (~) beginnt, dann werden alle Zeichen von der Tilde bis zum nächsten unquotierten Schrägstrich (/) oder, sofern kein solcher Schrägstrich vorkommt, alle Zeichen des Wortes als Tilde-Präfix interpretiert.
Wenn keines der Zeichen eines Tilde-Präfixes quotiert ist, dann werden die der Tilde folgenden Zeichen des Präfixes als möglicher Login-Name (Nutzer-Name) angesehen. Ist dieser nicht leer, wird das komplette Tilde-Präfix durch den absoluten Pfad zum Home-Verzeichnis des benannten Nutzers ersetzt, sofern dieser Nutzer existiert. Existiert er nicht, bleibt das Tilde-Präfix wie generell beim Fehlschlagen der Tilde-Expandierung unverändert erhalten.
Im folgenden Beispiel wird unterstellt, dass der Nutzer otto existiert und das Home-Verzeichnis /home/otto hat, wogegen die Nutzer max und otto1 nicht existieren:
~otto # expandiert zu /home/otto ~max # ~max bleibt erhalten ~otto1 # ~otto1 bleibt erhalten; dass der Wortanfang otto # als Nutzer existiert, spielt hier keine Rolle
Wenn es sich beim Nutzer-Namen um den leeren String handelt, dann wird die Tilde durch den Wert der Shell-Variablen HOME ersetzt, die normalerweise den absoluten Pfad zum Home-Verzeichnis des aktuellen Nutzers enthält. Wenn HOME nicht gesetzt ist, wird die Tilde durch den Pfad zum Home-Verzeichnis des Nutzers ersetzt, der die Shell ausführt.
Die folgende Tabelle zeigt einige spezielle Tilde-Präfixe und erläutert, durch welchen Wert sie bei der Expandierung ersetzt werden:
~+ | Wert der Shell-Variablen PWD (die normalerweise den absoluten Pfad zum aktuellen Verzeichnis enthält), sofern diese existiert |
~- | Wert der Shell-Variablen OLDPWD (die normalerweise den absoluten Pfad zum zuvor aktuellen Verzeichnis enthält), sofern diese existiert |
~N | Eintrag des Verzeichnis-Stacks mit Index N |
~+N | Alternativ-Notation für ~N |
~-N | Eintrag des Verzeichnis-Stacks mit Index N vom Ende des Stacks gezählt, also analog der Anzeige beim Kommando dirs -N |
Bei jeder Variablenzuweisung unterliegen alle Tilde-Präfixe, die unmittelbar einem Doppelpunkt (:) oder einem Gleichheitszeichen (=) folgen, der Tilde-Expandierung. Bei Zuweisungen an die Shell-Variablen PATH, MAILPATH und CDPATH, deren Wert eine durch Doppelpunkte getrennte Liste von Dateien bzw. Verzeichnissen ist, kann man daher Pfadnamen verwenden, die zur Abkürzung eine Tilde enthalten. Diese Kurzangabe wird in die entsprechende Langform expandiert, wie folgendes Beispiel zeigt:
# Der aktuelle Nutzer ist max mit Home-Verzeichnis /home/max. # Nutzer otto hat das Home-Verzeichnis /home/otto: PATH=/bin:/usr/bin:~otto:~ # expandiert zu /bin:/usr/bin:/home/otto:/home/max
Weitere Beispiele, bei denen wir den Standardfall unterstellen, dass die Shell-Variablen HOME, PWD und OLDPWD sinnvoll gesetzt sind, also den absoluten Pfad zum Home-Verzeichnis des aktuellen Nutzers, zum aktuellen Verzeichnis der Shell sowie zum zuvor aktuellen Verzeichnis der Shell enthalten:
~ # expandiert zu $HOME ~/.bashrc # expandiert zu $HOME/.bashrc ~otto # expandiert zum absoluten Pfad des Home-Verzeichnisses von Nutzer otto ~otto/xxx # expandiert zum absoluten Pfad der Datei xxx im Home-Verzeichnis von otto ~+ # expandiert zu $PWD ~- # expandiert zu $OLDPWD ~0 # expandiert zum ersten Element des Verzeichnis-Stacks ~+0 # dito ~-0 # expandiert zum letzten Element des Verzeichnis-Stacks
Parameter- bzw. Variablen-Expandierung
Die Parameter-Expandierung wird (wie auch die Kommando-Substitution sowie die arithmetische Expandierung) durch ein Dollarzeichen ($) eingeleitet. Der dem Dollarzeichen folgende Name des zu expandierenden Parameters kann stets in unquotierte geschweifte Klammern eingeschlossen werden.
Ein Konstrukt der Form
${Name}
wird durch den Wert desjenigen Parameters ersetzt, dessen Name im Konstrukt angegeben wurde.
Sofern dieser Parameter nicht existiert, ersetzt die Bash das Konstrukt standardmäßig durch den leeren String. Wenn allerdings die set-Option u bzw. nounset aktiviert ist, erfolgt bei nicht existierendem Parameter keine Substitution, sondern die Ausgabe eine Fehlermeldung der Form
var: unbound variable
Eine nicht-interaktive Shell wird anschließend abgebrochen.
Die geschweiften Klammern können generell verwendet werden, sind aber nur dann notwendig, wenn
die Nummer eines zu expandierenden Positionsparameters größer als 9 ist und damit aus mehr als einem Zeichen besteht,
hinter dem Namen einer einfachen Variablen ein Zeichen folgt, das noch zum Namen gehören könnte, also ein alphanumerisches Zeichen oder ein Unterstrich (_) oder
eines der unten genannten speziellen Konstrukte zum Einsatz kommt, z.B. zum Zugriff auf Feld-Elemente, zur Bestimmung der String-Länge eines Variablen-Wertes sowie zur Substitution von Teilen des Wertes im Rahmen der Expandierung.
Beispiele:
echo $1 $9 ${10} # Positionsparameter 1, 9, 10 (nummerierte Argumente # eines Shell-Skripts oder einer Shell-Funktion); # Nummern über 9 sind immer in geschweifte Klammern einzuschließen echo "$*" # alle Positionsparameter als ein String echo "$@" # alle Positionsparameter als Liste echo "$PATH" # Inhalt der Shell-Variablen PATH echo "$zahl" # Inhalt der nutzereigenen Variablen zahl echo "${laenge}m" # Da hier hinter dem Variablen-Namen ein m folgt, das noch zum # Variablennamen gehören könnte, muss man den Variablennamen in geschweifte # Klammmern einschließen. Ohne Klammern würde man hier auf den Inhalt der Variablen # laengem zugreifen. Sicherheitshalber kann man auch generell die Klammern angeben.
Wenn geschweifte Klammern genutzt werden, dann wird als abschließende Klammer immer das erste hinter der öffnenden Klammer folgende Zeichen } interpretiert, das unquotiert ist und sich nicht innerhalb einer eingebetteten arithmetischen Expandierung, Kommando-Substitution oder Parameter-Expandierung befindet. Im folgenden Beispiel kommen diese drei Expandierungen in der genannten Reihenfolge vor:
feld=(1 2 3) index=1 echo ${feld[$((${index} + 0))]} echo ${feld[$(echo ${index})]} echo ${feld[${index}]}
Zunächst wurde hier ein dreielementiges Feld mit den Elementen 1, 2 und 3 angelegt. Die Variable index erhält den Wert 1. Über sie wird in den folgenden drei echo-Kommandos jeweils auf das Element mit dem Index 1 zugegriffen, also auf das zweite Feld-Element. Das jeweils äußere Paar geschweifter Klammern ist rot und das innere blau markiert.
Anmerkung: In allen drei Fällen des obigen Beispiels kann die Verwendung der geschweiften Klammern in den eingebetteten Expandierungen entfallen. Sie wurden also nur zu Demonstrationszwecken notiert, können aber in anderen Konstrukten durchaus nötig sein. Z.B. wären sie erforderlich, wenn wir den Wert des Positionsparameters 11 als Index nutzen möchten. Das könnte dann so aussehen:
echo ${feld[$((${11} + 0))]}
In den Konstrukten der Parameter-Expandierung kann man zwischen den geschweiften Klammern nicht nur Parameter-Namen, sondern zusätzlich auch verschiedene Operatoren und ggf. zugehörige Operanden angeben, um z.B. nicht den Wert des Parameters zu substituieren, sondern dessen Länge oder einen Teil seines Wertes. Nachfolgend werden alle von der Bash unterstützten Operatoren besprochen.
Bevor wir im Text die verfügbaren Operatoren detailliert diskutieren und an Beispielen erläutern wollen, folgt zunächst eine Tabelle, die einen Überblick über die Operatoren gibt und dem erfahreneren Anwender ein schnelleres Nachschlagen der Syntax ermöglichen soll:
Indirekte Expandierung |
${!Name}
|
Test auf leere bzw. nicht existierende Parameter |
${Name:-Wort} Verwendung eines Vorzugs-Werts ${Name:=Wort} Zuweisung eines Vorzugs-Werts ${Name:?Wort} Fehler-Anzeige bei leerem/ungesetztem Parameter ${Name:+Wort} Nutzung eines alternativen Wertes Operatoren ohne Doppelpunkt testen nur, ob der Parameter gesetzt ist. |
Substring-Expandierung |
${Name:Offset} ${Name:Offset:Länge} |
Expandierung von Variablen-Namen |
${!Präfix*} ${!Präfix@} |
Expandierung der Feld-Indexe |
${!Name[@]} ${!Name[*]} |
Expandierung der Länge von Variablen-Werten sowie der Anzahl von Feld-Elementen |
${#Name}
${#*}
${#@}
Anzahl der Positionsparameter
${#Feld-Name[*]}
${#Feld-Name[@]}
Anzahl der Feld-Elemente
|
Teil-String-Löschung mittels Pfadnamens-Mustern |
${Name#Wort} löscht den kürzesten passenden String von links ${Name##Wort} löscht den längsten passenden String von links ${Name%Wort} löscht den kürzesten passenden String von rechts ${Name%%Wort} löscht den längsten passenden String von rechts Die Teil-String-Löschung ist auf Felder anwendbar. |
Teil-String-Ersetzung mittels Pfadnamens-Mustern |
${Name/Muster/Ersatz-String} ersetzt den ersten auf Muster passenden Teil-String ${Name//Muster/Ersatz-String} ersetzt alle auf Muster passenden Teil-Strings Generell wird immer der längste passende Teil-String ersetzt. Ein # verankert das Muster am linken Rand und ein % am rechten Rand des Inhalts von Name. Die Teil-String-Ersetzung ist auf Felder anwendbar. |
Wenn das erste Zeichen eines Parameter-Namens ein Ausrufezeichen (!) ist, dann erfolgt eine indirekte Expandierung bzw. ein indirekter Variablen-Zugriff, sofern es sich nicht um die speziellen Konstrukte ${!Präfix*} und ${!Name[@]} handelt, die wir unten noch besprechen.
Das Ausrufezeichen muss bei der indirekten Expandierung unmittelbar hinter der öffnenden geschweiften Klammer notiert werden. Der hinter dem Ausrufezeichen folgende String wird als Name derjenigen Variablen interpretiert, die den Namen der zu expandierenden Variablen enthält. Letztere wird dann wirklich expandiert, wie folgendes Beispiel zeigt:
name=Hugo varname=name echo ${!varname}
Das Kommando echo gibt hier also nicht den Wert der Variablen varname, sondern den Wert der Variablen name und somit den String Hugo aus, da varname den Wert name hat.
Test auf leere bzw. nicht existierende Parameter
In jedem der nachfolgend genannten vier Konstrukte unterliegt das Wort einer Tilde- und Parameter-Expandierung, einer Kommando-Substitution sowie einer arithmetischen Expandierung. Die Bash testet bei diesen Konstrukten, ob ein Parameter nicht gesetzt ist, also nicht existiert, oder leer ist, also den leeren String als Wert hat. Wenn man den Doppelpunkt in den vier Operatoren
:- := :? :+weglässt, wird nur noch getestet, ob der Parameter gesetzt ist.
${Name:-Wort}
Verwendung eines Vorzugs-Werts: Wenn der genannte Parameter nicht existiert oder leer ist, wird das gesamte Konstrukt durch die Expandierung von Wort ersetzt, andernfalls durch den Wert des Parameters.${Name:=Wort}
Zuweisung eines Vorzugs-Werts: Wenn der genannte Parameter nicht existiert oder leer ist, dann wird ihm die Expandierung von Wort zugewiesen. Das gesamte Konstrukt wird dann durch den neuen Wert des Parameters ersetzt. Speziellen und Positionsparametern können auf diese Weise allerdings keine neuen Werte zugewiesen werden.${Name:?Wort}
Fehler-Anzeige bei leerem oder ungesetztem Parameter: Wenn der genannte Parameter existiert, wird das gesamte Konstrukt durch seinen Wert ersetzt. Sofern er aber nicht existiert oder leer ist, dann wird die Expandierung von Wort oder, falls Wort nicht existiert, eine Fehlermeldung in den Standard-Fehlerstrom ausgegeben. Eine nicht-interaktive Shell wird anschließend beendet.${Name:+Wort}
Nutzung eines alternativen Wertes: Wenn der genannte Parameter nicht existiert oder leer ist, dann wird das gesamte Konstrukt durch nichts (also den leeren String), andernfalls durch die Expandierung von Wort ersetzt.Beispiele:
${name:-XYZ} # falls name leer/ungesetzt ist, dann substituiere XYZ, sonst # $name ${name:=XYZ} # falls name leer/ungesetzt ist, dann weise XYZ an name zu # und substituiere diesen zugewiesenen Wert, sonst $name ${name:?leer} # falls name leer/ungesetzt ist, dann Ausgabe der # Fehlermeldung "bash: name: leer" nach stderr, sonst # substituiere $name ${name:?} # falls name leer/ungesetzt ist, dann Ausgabe der # Fehlermeldung "bash: name: parameter null or not set" nach stderr, # sonst substituiere $name ${name:+XYZ} # falls name leer/ungesetzt ist, dann substituiere nichts # (leere Zeichenkette), sonst XYZ # Wie oben erwähnt, ist der Doppelpunkt bei allen 4 Varianten optional. Wird er # weggelassen, dann wird nur noch auf ungesetzt und nicht auch auf leer getestet: echo ${name-XYZ} # falls name ungesetzt ist (also die Variable nicht # existiert), dann substituiere XYZ, sonst $name # Eine Schachtelung derartiger Konstrukte ist möglich: ${FCEDIT:=${EDITOR:=vim}} # Wenn die Variable EDITOR leer/ungesetzt ist, erhält sie den Wert vim. # Dieser wird nachfolgend an die Variable FCEDIT zugewiesen, sofern diese # ebenfalls leer/ungesetzt ist. Das Gesamt-Konstrukt wird danach durch den Wert # von FCEDIT ersetzt.
Durch ein Konstrukt der Art
${Name:Offset} ${Name:Offset:Länge}
ermöglicht die Bash eine Teil-String-Expandierung, d.h. die Ersetzung eines solchen Ausdrucks durch einen Teil des Werts des genannten Parameters. Dieser Teil beginnt beim Zeichen mit dem Index Offset, wobei das erste Zeichen den Index 0 hat. Die optionale Angabe Länge gibt die Maximallänge des zu bildenden Teil-Strings an. Fehlt sie oder übersteigt sie die Anzahl der verfügbaren Zeichen, dann wird der komplette Teil-String ab Index Offset geliefert.
Beispiele:
name=ABCDEFGHIJ echo ${name:3} # Der Wert von name wird ab Index 3 bis zum Ende ausgegeben, # also konkret DEFGHIJ. echo ${name:3:2} # Der Wert von name wird ab Index 3 in der Länge 2 Zeichen # ausgegeben, also konkret DE.Länge und Offset sind arithmetische Ausdrücke. Der Wert von Länge muss eine nichtnegative ganze Zahl sein. Andernfalls erscheint eine Fehlermeldung der Art
bash: -1: substring expression < 0Falls der Wert von Offset eine negative ganze Zahl ist, wird er als Index vom rechten Rand des Strings interpretiert.
Anmerkung: Wenn der als Offset genutzte Ausdruck mit einem Minus-Zeichen beginnt, was typischerweise bei negativen ganzzahligen Konstanten der Fall ist, dann darf das Minuszeichen dem Doppelpunkt nicht unmittelbar folgen, weil die sonst entstehende Zeichenfolge :- als der o.g. Operator zur Verwendung eines Vorzugswertes bei leerem oder nicht existierendem Parameter gewertet wird. Um das Minuszeichen vom Doppelpunkt zu trennen, kann man z.B. ein Leerzeichen nach dem Doppelpunkt einfügen oder den Ausdruck in runde Klammern einschließen.
Beispiele:
name=ABCDEFGHIJ echo ${name: -3} # expandiert zu HIJ echo ${name:(-3)} # expandiert zu HIJ ${name:-3} # expandiert zu ABCDEFGHIJ, da kein Leerzeichen vor dem Minus steht; # Hier wird also die rot gekennzeichnete Zeichenfolge als Operator :- gewertet. # Da die Variable name nicht leer ist, kommt der Vorzugswert 3 nicht # zur Anwendung. unset name ${name:-3} # expandiert zu 3, da name nicht existiert name=ABCDEFGHIJ ${name:0-3} # expandiert zu HIJ ${name:0-6:4} # expandiert zu EFGH ${name:-6:4} # expandiert zu ABCDEFGHIJ unset name ${name:-6:4} # expandiert zu 6:4Bei Angabe des Parameter-Namens @ werden als Resultat der Expandierung die Positionsparameter ab Index Offset in der Anzahl Länge geliefert, wobei hier im Gegensatz zu den anderen Arten der Teil-String-Expandierung die Indexierung bei 1 und nicht bei 0 beginnt. Fehlt die Angabe der Länge, liefert die Bash alle Positionsparameter ab Index Offset.
Beispiele:
set eins zwei drei vier fünf # Positionsparameter $1 bis $5 mit den genannten Werten füllen ${@:4} # expandiert zu vier fünf ${@:2:3} # expandiert zu zwei drei vier ${@: -1} # expandiert zu fünf ${@:0-3:2} # expandiert zu drei vierWenn es sich bei dem Parameter-Namen um einen durch @ oder * indexierten Feld-Namen handelt, werden als Resultat der Expandierung die Feld-Elemente ab Index Offset in der Anzahl Länge geliefert. Die Indexe @ und * sind dabei von der Wirkung her identisch.
Beispiele:
feld=(eins zwei drei vier fünf) # feld mit genau den genannten 5 Werten initialisieren ${feld[@]:3} # expandiert zu vier fünf ${feld[@]:1:3} # expandiert zu zwei drei vier ${feld[@]: -1} # expandiert zu fünf ${feld[@]:0-3:2} # expandiert zu drei vier
Expandierung von Variablen-Namen
Konstrukte der Form
${!Präfix*} ${!Präfix@}
werden durch die Liste der Namen aller Variablen ersetzt, die mit dem angegebenen Präfix beginnen. Das erste Zeichen der Shell-Variable IFS dient als Trenner zwischen den Listen-Elementen. Wenn IFS leer ist, wird ein einzelnes Leerzeichen als Trenner genutzt. Hat IFS den leeren String als Wert, wird gar kein Trenner eingefügt. Wenn man das zweite Konstrukt (jenes mit @) in Doppelapostrophe einschließt, wird jeder zum Muster passende Variablen-Name als separates Wort expandiert.
Beispiele:
${!W*} # Liste aller mit W beginnenden Variablen-Namen, z.B. # WINDOWID WINDOWMANAGER (hier also 2 Wörter) ${!W@} # dito "${!W@}" # dito "${!W*}" # ein einziges Wort statt einer Liste, das alle # mit W beginnenden Variablen-Namen enthält, z.B. # WINDOWID WINDOWMANAGER
Wenn Name in den Konstrukten
${!Name[@]} ${!Name[*]}
eine Feld-Variable ist, dann wird das Konstrukt durch die Liste aller Indexe des Feldes Name substituiert. Handelt es sich bei Name um kein Feld, dann ersetzt die Bash das Konstrukt durch 0, wenn die Variable Name existiert, bzw. durch einen leeren String, wenn sie nicht existiert.
Wenn man obiges Konstrukt in Doppelapostrophe einschließt, wird beim Index @ jeder Feld-Index zu einem separaten Wort expandiert. Bei Verwendung von Index * statt @ entsteht als Resultat dagegen ein einziges Wort, das alle Feld-Indexe enthält, wobei sie jeweils durch das erste Zeichen von IFS voneinander getrennt sind. Wenn IFS leer ist, wird ein einzelnes Leerzeichen als Trenner genutzt. Hat IFS den leeren String als Wert, wird gar kein Trenner eingefügt.
Beispiele:
feld=(eins zwei drei vier fünf) feld[10]=zehn ${!feld[@]} # expandiert zu den 6 Wörtern 0 1 2 3 4 10 ${!feld[*]} # dito "${!feld[@]}" # dito "${!feld[*]}" # expandiert zu einem Wort 0 1 2 3 4 10
Expandierung der Länge von Variablen-Werten sowie der Anzahl von Feld-Elementen
Ein Konstrukt der Form
${#Name}
wird durch die Länge (Zeichenanzahl) des Wertes des Parameters Name ersetzt. Wenn als Name die Zeichen * oder @ angegeben werden, wird der gesamte Ausdruck durch die Anzahl der Positionsparameter ersetzt.
Wenn es sich bei Name um ein Konstrukt der Form
Feld-Name[@] Feld-Name[*]handelt, dann wird der gesamte Ausdruck durch die Anzahl der Elemente des benannten Feldes ersetzt.
Beispiele:
${#PATH} # Länge des Variableninhalts von PATH ${#@} # Anzahl der Positionsparameter ${#*} # dito feld=(eins zwei drei vier fünf) ${#feld[@]} # Anzahl der Elemente des Feldes feld (hier: 5) ${#feld[*]} # dito
Teil-String-Löschung mittels Pfadnamens-Mustern
In Konstrukten der Form
${Name#Wort} ${Name##Wort}
wird zunächst das Wort expandiert. Das Ergebnis dieser Expandierung wird als Pfadnamens-Muster interpretiert. Wenn dieses Muster auf den Anfang des Wertes des Parameters Name passt, dann wird das Gesamt-Konstrukt durch den Wert des angegebenen Parameters ersetzt, wobei der dem Muster entsprechende Teil-String von links gelöscht wird.
Bei Variante 1 (nur ein Doppelkreuz) wird der kürzeste auf das Muster passende Teil-String gelöscht, bei Variante 2 (zwei Doppelkreuze) dagegen der längste passende Teil-String.
Wenn man als Name das Zeichen @ oder * verwendet, dann wird die Teil-String-Löschung der Reihe nach auf alle Positionsparameter angewendet. Ergebnis der Gesamt-Expandierung ist die Liste der durch Teil-String-Löschung expandierten Positionsparameter.
Analog gilt dies für Felder. Falls also Name die Form
Feld-Name[@] Feld-Name[*]hat, wird die Teil-String-Löschung der Reihe nach auf alle Feld-Elemente angewendet. Ergebnis der Gesamt-Expandierung ist dann die Liste der durch Teil-String-Löschung expandierten Feld-Elemente.
Sofern man statt der gerade diskutierten Teil-String-Löschung von links eine Teil-String-Löschung von rechts realisieren möchte, so muss man den Operator Doppelkreuz jeweils durch ein Prozentzeichen ersetzen. Man erhält also Konstrukte der Form
${Name%Wort} ${Name%%Wort}
Bei einem Prozentzeichen wird der kürzeste und bei zwei Prozentzeichen der längste auf das Muster passende Teil-String gelöscht. Ansonsten gelten alle obigen Aussagen zur Teil-String-Löschung von links analog.
Teil-String-Ersetzung mittels Pfadnamens-Mustern
Neben der Teil-String-Löschung bietet die Bash auch noch eine Teil-String-Ersetzung an, die man mit Konstrukten der Form
${Name/Muster/Ersatz-String} ${Name//Muster/Ersatz-String}
ausdrückt. Hierbei wird das Muster auch wieder als Pfadnamens-Muster interpretiert. Als Ergebnis der Gesamt-Expandierung ergibt sich der Wert des Parameters Name, wobei der längste auf Muster passende Teil-String durch den Ersatz-String ersetzt wird.
Bei Variante 1 (mit einem Schrägstrich hinter dem Parameter-Namen) wird nur der erste Teil-String ersetzt, der auf das Muster passt. Dagegen werden bei Variante 2 (mit zwei Schrägstrichen hinter dem Parameter-Namen) alle Teil-Strings ersetzt, die auf das Muster passen.
Wenn das Muster mit einem Doppelkreuz (#) beginnt, muss es am linken Rand des expandierten Parameter-Wertes passen. Falls es mit einem Prozentzeichen (%) beginnt, muss es dagegen am rechten Rand des expandierten Parameter-Wertes passen.
Wenn der Ersatz-String leer ist, wird der auf das Muster passende Teil-String durch die leere Zeichenfolge ersetzt, d.h. gelöscht. In diesem Falle kann man den auf das Muster folgende Schrägstrich auch weglassen.
Analog der oben besprochenen Teil-String-Löschung wird auch die Teil-String-Ersetzung der Reihe nach auf alle Positionsparameter angewendet, wenn Name dem Zeichen @ oder * entspricht.
Entsprechend bezieht sich die Teil-String-Ersetzung bei Konstrukten der Form
Feld-Name[@] Feld-Name[*]der Reihe nach auf alle Elemente des benannten Feldes.
Beispiele:
name=abcd_abcd # Teil-String-Löschung von links: ${name#*a} # löscht den kleinsten auf *a passenden String # Ergebnis: bcd_abcd ${name##*a} # löscht den größten auf *a passenden String # Ergebnis: bcd ${name##*X} # löscht den größten auf *X passenden String # Ergebnis: abcd_abcd, also $name, da das Muster nicht passte ${name#?} # löscht den kleinsten auf ? passenden String # Ergebnis: bcd_abcd ${name##?} # löscht den größten auf ? passenden String # Ergebnis: bcd_abcd, also wie bei der vorigen Expandierung, da ? # nur genau einem Zeichen entspricht # Teil-String-Löschung von rechts: ${name%b*} # löscht den kleinsten auf b* passenden String # Ergebnis: abcd_a ${name%%b*} # löscht den größten auf b* passenden String # Ergebnis: a ${name%%X*} # löscht den größten auf X* passenden String # Ergebnis: abcd_abcd, also $name, da das Muster nicht passte ${name%?} # löscht den kleinsten auf ? passenden String # Ergebnis: abcd_abc ${name%%?} # löscht den größten auf ? passenden String # Ergebnis: abcd_abc, also wie bei der vorigen Expandierung, da ? # nur genau einem Zeichen entspricht # Mit der Teil-String-Löschung kann man die Wirkung der Kommandos # basename und dirname nachbilden: filename=/var/log/messages.1 ${filename##*/} # entspricht basename /var/log/messages.1 ${filename%/*} # entspricht dirname /var/log/messages.1 # Das externe Kommando dirname liefert einen Punkt als Resultat, wenn sein Argument # keinen Schrägstrich enthält. Dies könnten wir in der Bash so nachbilden: if [[ $filename == */* ]] then # Schrägstrich vorhanden echo ${filename%/*} else # kein Schrägstrich vorhanden echo . fi # Das Kommando basename gestattet optional die Entfernung eines Suffixes. # So erhält man bei # # basename /var/log/messages.1 .1 # # den String messages als Ergebnis. # # Dieses Verhalten lässt sich in der Bash nicht unmittelbar nachbilden, da # die Teil-String-Löschung im Gegensatz zur Z-Shell nicht schachtelbar ist. # Allerdings sind derartige verschachtelte Ausdrücke nicht besonders gut # lesbar. # # Bei der Bash könnte man obige Aufgabe in 2 Schritten erledigen: # # tmp=${filename##*/} # ${tmp%.1} # # Die Z-Shell gestattet dagegen den Ausdruck # ${${filename##*/}%.1} # Teil-String-Ersetzung: ${name/bc/x} # ersetzt erstes Auftreten von bc durch x # Ergebnis: axd_abcd ${name//bc/x} # ersetzt jedes Auftreten von bc durch x # Ergebnis: axd_axd ${name/#a/g} # ersetzt a durch g am Anfang von $name # Ergebnis: gbcd_abcd ${name/%d/g} # ersetzt d durch g am Ende von $name # Ergebnis: abcd_abcg ${name/%b/g} # ersetzt b durch g am Ende von $name # Ergebnis: abcd_abcd, also $name, da das Muster nicht passte # Wie oben schon erwähnt, funktioniert die Ersetzung von Teilzeichenketten auch # bei Feldern. Dabei wird die Ersetzung auf jedes Feld-Element angewendet. files=(/etc/*) # Feld aller Dateinamen in /etc echo ${files[@]##*/} # den größten auf */ passenden String von links entfernen, # also basename anwenden echo ${files[@]/etc/xxx} # ersetzt etc durch xxx # Analog können wir die Positionsparameter nutzen: set eins zwei drei vier echo ${*#?} # Ausgabe: ins wei rei ier # Bei der Expandierung wurde das erste Zeichen jedes Positionsparameters # gestrichen. echo ${@#?} # dito
Ziel der Kommando-Substitution ist die Ersetzung eines Kommandos durch die von diesem Kommando generierte Ausgabe. Hierfür gibt es zwei Formate:
$(Kommando)
`Kommando...`
Das von der Bourne-Shell stammende Format 2 gilt als veraltet und sollte (speziell in Skripten) möglichst nicht mehr verwendet werden. Für den Einsatz der alten Form in interaktiven Shells spricht dagegen der geringere Tipp-Aufwand sowie die Tatsache, dass man dort Kommando-Substitutionen oft nicht schachteln will oder muss, so dass dann die leichtere Schachtelbarkeit als Hauptvorteil der neuen Form nicht ins Gewicht fällt.
Die neue Form stammt von der Korn-Shell und steht auch in der Z Shell sowie der ash zur Verfügung, nicht aber bei der tcsh.
Das zwischen den runden Klammern bzw. den Backticks angegebene Kommando wird im Rahmen der Substitution zunächst ausgewertet und ausgeführt. Der gesamte Ausdruck zur Kommando-Substitution wird dann durch die Daten ersetzt, die das ausgeführte Kommando auf die Standard-Ausgabe ausgab, wobei das abschließende Newline-Zeichen entfernt wird. Eingebettete Newlines bleiben erhalten, können aber durch die Aufspaltung in Wörter eliminiert werden.
Anmerkung: In praktischen Tests des Autors hat sich gezeigt, dass im Rahmen der Kommando-Substitution neben dem abschließenden Newline-Zeichen auch alle NUL-Bytes entfernt werden, wie folgendes Beispiel zeigt:
files=$(find . -print0)
Wegen der Option -print0 werden die Dateinamen in der von find generierten Dateinamens-Liste durch NUL-Bytes voneinander getrennt. Die Shell eliminiert diese Trenner komplett.
Bei der alten Form behält der Backslash seine ursprüngliche Bedeutung (das Zeichen Backslash), es sei denn, ihm folgt ein $, Backtick (`) oder Backslash. Vor diesen drei Zeichen hat er seine Sonderbedeutung als Quotierungs-Zeichen. Das erste unquotierte Backtick-Zeichen beendet die Kommando-Substitution.
Bei der neuen Form gehören alle Zeichen zwischen den runden Klammern zum Kommando. Keines von ihnen hat eine Sonderbedeutung.
Wie schon erwähnt, sind Kommando-Substitutionen schachtelbar. Bei der neuen Form ist dies sehr einfach realisierbar. In der alten Form muss man die Backticks der inneren Substitution geeignet durch Quotierung schützen.
Beispiele:
# Verwendung der neuen und alten Form: echo $(date) '###' `uptime` # Hier werden an das Kommando echo 3 Argumente übergeben: # Argument 1: die Ausgabe des Kommandos date # Argument 2: der Trenner ### # Argument 3: die Ausgabe des Kommandos uptime # einfache Schachtelbarkeit bei der neuen Form: echo $(ls $(pwd)) # Die Ausgabe von pwd wird an ls übergeben. # Anschließend wird die Ausgabe von ls an echo übergeben. # zwei inhaltlich identische Varianten für geschachtelte Kommando-Substitutionen: wc -l $(file $(cat filenames) | grep 'Bourne.* shell script' | cut -d: -f1) wc -l `file \`cat filenames\` | grep 'Bourne.* shell script' | cut -d: -f1` # Die in der Datei filenames gespeicherten Dateinamen werden als Argumente an das # Kommando file übergeben, um den Typ dieser Dateien zu ermitteln. Mittels grep # werden nur jene herausgefiltert, bei denen es sich um Bourne-Shell- bzw. # Bash-Skripte handelt. Das Kommando cut ermittelt das erste Feld jeder Zeile, # also den Dateinamen. # Die Dateinamen der Skripte werden an das Kommando wc übergeben, das # wegen der Option -l die Zeilenzahl jedes Skripts bestimmt. Wenn wc # mehr als ein Argument hat, gibt es am Schluss auch noch eine Summe aus, im # konkreten Falle die Summe aller Zeilen der betreffenden Skripte. # # Die bei der alten Form notwendige Quotierung der inneren Backticks wurde rot # hervorgehoben. # Alternativ-Notation zu $(cat datei) echo $(< datei) # Speziell in interaktiven Shells kann man die Kommando-Substitution # oft gut mit der History-Expandierung kombinieren. Nehmen wir an, wir wollen # alle Dateien eines Teilbaums mit dem Editor vim editieren, in denen # ein bestimmtes Muster vorkommt, nach dem mit grep gesucht werden # soll. Wenn man sich anfangs nicht sicher ist, ob man das Muster geeignet # formuliert hat, kann man sich die Ausgaben von grep auf die # Standard-Ausgabe ausgeben lassen und dort optisch kontrollieren. # # Sobald das Muster korrekt ist, lässt man sich durch die Option -l # von grep nur die Dateinamen ausgeben und leitet diese via # Kommando-Substitution an den Editor weiter: grep '\bcache\b' * # Suche nach dem Wort cache in allen Dateien des aktuellen Verzeichnisses grep -l '\bcache\b' * # wie oben, aber mit Option -l vim `!!` # Aufruf des Vim mit allen von grep -l generierten Dateinamen # Die Option -l kann man auch direkt via History-Substitution einfügen: vim `!!:s/ / -l /` # Hier wird das erste Leerzeichen, also normalerweise jenes hinter # dem Kommando-Namen, durch die Zeichenfolge # # Leerzeichen -l Leerzeichen # # ersetzt. Diese Angabe ist aber umständlich einzugeben und funktioniert nur, # wenn vor grep kein Leerzeichen steht. Man kommt daher sicher schneller, # wenn man das erste grep-Kommando via Cursor-Taste in den Kommandozeilen-Editor # zurückholt, dort das -l ergänzt, das Kommando erneut ausführt und dann # die Kommando-Substitution anwendet. Alternativ kann man natürlich auch das # grep-Kommando zurückholen und die fehlenden Teile ergänzen, also vorn vim # sowie einen Backtick und hinten den Backtick.
Die arithmetische Expandierung gestattet die Auswertung arithmetischer Ausdrücke sowie die Substitution dieser Ausdrücke durch ihr Ergebnis. Dabei steht allerdings lediglich eine Ganzzahl-Arithmetik (Integer-Arithmetik) fester Länge, aber keine Gleitpunkt-Arithmetik zur Verfügung. Für ganzzahlige Berechnungen kann und sollte man die internen Möglichkeiten der Bash benutzen. Der Rückgriff auf externe Programme wie expr ist hier also nicht erforderlich.
Die arithmetische Expandierung wird für Konstrukte der Form
$((Ausdruck))
durchgeführt. Der arithmetische Ausdruck wird so behandelt, als ob er in Doppelapostrophe eingeschlossen wäre. Man darf diese impliziten Doppelapostrophe allerdings nicht explizit angeben.
Arithmetische Ausdrücke sind schachtelbar. Alle Operanden eines arithmetischen Ausdrucks unterliegen der Parameter-Expandierung sowie der Kommando-Substitution. Um innerhalb des Ausdrucks auf den Wert einer Variablen zuzugreifen, genügt es, den Variablen-Namen anzugeben. Man kann ein Dollarzeichen voransetzen, muss es aber nicht tun.
Details zur Auswertung arithmetischer Ausdrücke sowie zu den verfügbaren Operatoren finden sich im Abschnitt Rechnen mit der Bash. Wenn der arithmetische Ausdruck ungültig ist, gibt die Bash eine Fehlermeldung aus und führt keine Expandierung durch.
Beispiele:
$((2**3)) # 2 hoch 3, also 8 $((16#11)) # Basis 16; Ergebnis ist 17, der Dezimalwert der Hexadezimal-Zahl 0x11 # drei nachfolgend genutzte Variablen initialisieren a=5 ausdr=1+2+3 # Zuweisung des Strings 1+2+3 an die Variable ausdr feld=(5 6 7) # Zugriff auf Variablen-Inhalte durch Parameter-Expandierung $((ausdr+a)) $(($ausdr+$a)) # Ergebnis: 11 # Wie oben erwähnt, kann man das Dollarzeichen vor Variablen-Namen # weglassen. Trotzdem erfolgt eine Parameter-Substitution. Daher sind # die beiden gezeigten Ausdrücke wertmäßig identisch. # Zugriff auf Feld-Elemente durch Parameter-Expandierung $((feld+5)) $((feld[0]+5)) $((${feld[0]}+5)) # Ergebnis: 10 # Die drei gezeigten Ausdrücke sind wertmäßig identisch. Zum Inhalt # des ersten Feld-Elements wird die Zahl 5 addiert. # Nutzung von Teil-String-Operationen bei der Parameter-Expandierung echo $((${ausdr:0:3}+a)) echo $((${ausdr%+3}+a)) # Ergebnis: 8 # Beide Ausdrücke sind wertmäßig identisch. Der obere Ausdruck nutzt eine # Teil-String-Bildung. Somit wird der links vom Operator + stehende # Operand durch den String 1+2 ersetzt, da dies die ersten 3 Zeichen # des Inhalts der Variablen ausdr sind. # Der untere Ausdruck übernimmt den Wert der Variablen ausdr und # löscht von rechts den Teil-String +3, so dass sein erster Operand # ebenfalls durch 1+2 ersetzt wird. # Beide Ausdrücke lauten nach allen Parameter-Expandierungen 1+2+5, # da die Variable a den Wert 5 hat. # Nutzung der Kommando-Substitution echo $((`echo 1+ausdr`+a)) echo $(($(echo 1+ausdr)+a)) # Ergebnis: 12 # Beide Ausdrücke sind wertmäßig identisch. Nach der Kommando-Substitution # lautet der erste Summand in beiden Fällen 1+ausdr. # Durch die nachfolgenden Parameter-Expandierungen ergibt sich der Ausdruck # 1+1+2+3+5. # Schachtelung arithmetischer Expandierungen echo $(($((9*2))+a)) # Ergebnis: 23 # Zunächst wird der innere Ausdruck 9*2 berechnet. Danach wird der # Ausdruck 18+5 ausgewertet. # Nutzung der internen Arithmetik statt eines externen Programms # # Ältere Shell-Skripte nutzen häufig das externe Kommando expr für # Integer-Berechnungen. Darauf kann und sollte man bei der Bash verzichten. # Hier zwei Beispiele für die Verwendung von expr im Rahmen der # Kommando-Substitution: echo "3 + 5 = `expr 3 + 5`" a=`expr $a + 1` # Die Bash-Äquivalente unter Verwendung der arithmetischen Expandierung # könnten so aussehen: echo "3 + 5 = $((3 + 5))" a=$((a+1)) # Für die Manipulation von arithmetisch genutzten Variablen sind # arithmetische Ausdrücke meist besser geeignet als arithmetische # Expandierungen (s. dazu Rechnen mit der Bash). # Die nachfolgenden arithmetischen Ausdrücke erhöhen alle den numerischen # Wert der Variablen a um 1. ((a++)) ((++a)) ((a = a + 1)) ((a += 1)) # Diese Ausdrücke kann man auch im Rahmen der arithmetischen Expandierungen # benutzen. Dann wird der Ausdruck durch seinen Wert ersetzt. Im Nebeneffekt # wird der Wert der Variablen a inkrementiert. Die folgenden Beispiele # erläutern dies: a=5 ; echo $((a++)) $a # Ausgabe: 5 6 # Wegen des Postfix-Inkrement-Operators liefert die arithmetische Expandierung # den Wert der Variablen a. Deren Wert wird erst anschließend inkrementiert. a=5 ; echo $((++a)) $a # Ausgabe: 6 6 # Wegen des Präfix-Inkrement-Operators wird zuerst der Wert der Variablen # a inkrementiert. Somit liefert die arithmetische Expandierung den Wert 6. a=5 ; echo $((a = a + 1)) $a # Ausgabe: 6 6 # Der Variablen a wird der Wert des Ausdrucks a+1 zugewiesen. # Der zugewiesene Wert ist gleichzeitig der Wert des Ausdrucks. Dieser wird # bei der arithmetischen Expandierung geliefert. a=5 ; echo $((a += 1)) $a # Ausgabe: 6 6 # Der Ausdruck a += 1 ist eine verkürzte Schreibweise für a = a + 1. # Genau wie bei den arithmetischen Ausdrücken kann man mit Hilfe des # Komma-Operators mehrere Ausdrücke syntaktisch zu einem Ausdruck # zusammenfassen, wobei die einzelnen Ausdrücke hintereinander ausgeführt # werden. Der Wert des letzten Ausdrucks wird dann bei der arithmetischen # Expandierung zurückgeliefert: echo $((a=9, b=10)) # Ausgabe: 10 # Der Variablen a wurde der Wert 9 und der Variablen b der Wert 10 # zugewiesen. Das gesamte Konstrukt wird bei der arithmetischen Expandierung # durch die 10 als Wert des letzten Ausdrucks ersetzt.
Die Prozess-Substitution steht nur auf Systemen zur Verfügung, die benannte Pipes oder die Adressierung offener Dateien über /dev/fd gestatten. Für Linux-Systeme ist dies z.B. der Fall.
Hinweise:
Im POSIX-Modus ist die Prozess-Substitution generell nicht verfügbar.
Dem Autor sind Bash-Installationen unter Linux bekannt, die die Prozess-Substitution nicht unterstützen, da sie vom Distributor bei der Kompilierung der Bash nicht aktiviert wurde. Durch eine geeignete Neu-Kompilierung kann dieses Problem behoben werden.
Die Prozess-Substitution verwendet folgende zwei Formate:
>(Liste)
<(Liste)
Die Liste wird so ausgeführt, dass ihre Eingabe oder Ausgabe mit einer benannten Pipe oder einer Datei in /dev/fd verbunden ist. Der Name dieser Datei ist das Ergebnis der Prozess-Substitution.
Beim Format 1 werden Daten, die in diese Datei geschrieben werden, als Eingabe an die Liste weitergegeben. Beim Format 2 wird die Ausgabe der Liste über die Datei zum Lesen bereitgestellt.
Beispiele:
cat /etc/{services,hosts} >| /tmp/services_hosts # Die Datei /tmp/services_hosts wird hier als Verkettung von /etc/services # und /etc/hosts erstellt. diff <(cat /etc/services) <(cat /tmp/services_hosts) # Mit diesem diff-Kommando wird unter Nutzung der Prozess-Substitution # die Differenz zwischen /etc/services und /tmp/services_hosts ermittelt. diff <(sort file1) <(sort file2) # Mit diff werden hier die beiden Dateien file1 und file2 in sortierter # Form verglichen, ohne die Ergebnisse der Sortierung als Dateien # zwischenzuspeichern. diff <(grep '^A' file1 | sed 's/$/|/') <(<file2 tr a-z A-Z) # Bei der ersten Prozess-Substitution wird hier eine Pipe verwendet. # Das diff-Kommando ermittelt die Unterschiede zwischen # - denjenigen Zeilen der Datei file1, die mit A # beginnen und an die hinten ein Pipe-Strich angehängt wurde, sowie # - dem in Großschreibung konvertierten Inhalt der Datei file2. diff <(bzcat /backup/file.bz2) file # Mit diesem diff-Kommando kann man prüfen, ob es sich bei der Datei # file um die unkomprimierte Original-Version der Backup-Datei # /backup/file.bz2 handelt. Die Dekomprimierung der Backup-Datei # erfolgt mittels bzcat via Prozess-Substitution, so dass das # Ergebnis der Dekomprimierung in keiner Datei zwischengespeichert # werden muss. paste -d: <(cut -d: -f1 /etc/passwd) <(cut -d: -f3 /etc/passwd)| tee >(cat >|/tmp/eins) >(cat >|/tmp/zwei) >/dev/null # Die Felder 1 und 3 von /etc/passwd werden extrahiert, neu verknüpft # und mittels tee an zwei cat-Prozesse weitergegeben, die # diese Daten in zwei Dateien speichern. Die Kopie der Daten, die tee # auf die Standard-Ausgabe schreibt, wird durch Umlenkung nach /dev/null # vernichtet. # # Anmerkung: Das erste Kommando der Pipe ließe sich einfacher so schreiben: # cut -d: -f1,3 /etc/passwd # Die obige Form wurde zur Demonstration der Prozess-Substitution gewählt. # Mittels Prozess-Substitution kann man in den Datenstrom einer Pipe # zusätzliche Daten einmischen: ls | cat - <(echo === Ende ===) | tee /tmp/lsout.txt # An die von ls erzeugte Dateinamens-Liste wird hier mittels # cat die Zeichenfolge === Ende === angefügt. Das erste Argument # von cat ist ein Minus und symbolisiert die Standard-Eingabe. Dadurch # leitet cat zunächst alle aus der Pipe gelesenen Daten an seine # Standard-Ausgabe und somit an das Folge-Kommando der Pipe weiter. # Anschließend wird die Standard-Ausgabe des Kommandos echo gelesen und # ebenfalls via Standard-Ausgabe an das Folge-Kommando der Pipe weitergeleitet.
Die Resultate von nicht in Doppelapostrophe eingeschlossenen Parameter-, Kommando- und arithmetischen Expandierungen werden von der Shell in einzelne Wörter zerlegt, sofern dies möglich ist. Als Worttrenner dienen dabei die einzelnen Zeichen der Variablen IFS.
Die Zeichenfolge
Leerzeichen Tabulatur Newline
ist der Vorzugs-Wert der Variablen IFS. Wenn sie nicht existiert, wird ihr von der Bash der Vorzugs-Wert implizit zugeordnet (die Bash verhält sich also so, als ob IFS den Vorzugs-Wert hätte).
Die drei Zeichen des Vorzugs-Werts nennt man auch IFS-Whitespace-Zeichen. Wenn sie zum Wert der Variablen IFS gehören, werden sie speziell behandelt. Allgemein gilt nämlich, dass jede Folge der aktuell in IFS enthaltenen IFS-Whitespace-Zeichen als Worttrenner fungiert und außerdem am Anfang und Ende jedes Wortes ignoriert wird.
Wenn also z.B. IFS ein Leerzeichen und einen Tabulator enthält, dann wird jede Folge von Leerzeichen und Tabulatoren als Worttrenner interpretiert und außerdem am Anfang und Ende jedes Wortes ignoriert. Das Newline-Zeichen wäre hier dagegen ein normales Zeichen, da es ja nicht in IFS gespeichert ist. Das Newline würde folglich als Bestandteil eines Wortes erhalten bleiben.
Jedes Zeichen im Wert von IFS, das kein IFS-Whitespace-Zeichen ist, dient zusammen mit allen unmittelbar vorangehenden oder nachfolgenden IFS-Whitespace-Zeichen, die ebenfalls zum Wert von IFS gehören, als Worttrenner.
Explizite leere Argumente ("" oder '') bleiben erhalten. Unquotierte implizite leere Argumente, die aus der Expandierung von Parametern resultieren, welche keinen Wert oder den leeren String als Wert haben, werden entfernt. Wenn dagegen ein Parameter ohne Wert bzw. mit leerem String als Wert innerhalb von Doppelapostrophen expandiert wird, bleibt das resultierende leere Argument erhalten.
Wenn IFS den leeren String als Wert hat, findet keine Aufspaltung in Wörter statt. Sofern keine Expandierung stattfindet, erfolgt ebenfalls keine Aufspaltung in Wörter.
Hinweis: Da Strings und somit die Werte von Bash-Variablen keine NUL-Bytes enthalten dürfen, kann auch IFS kein NUL-Byte enthalten. Die Zuweisung
IFS=$'\000'
führt folglich dazu, dass IFS den leeren String als Wert bekommt, so dass keine Aufspaltung in Wörter erfolgt. Wenn man das NUL-Byte als Worttrenner benötigt, um z.B. die Ausgaben des mit der Option -print0 aufgerufenen Kommandos find zu bearbeiten, sollte man auf andere geeignete Werkzeuge zurückgreifen. Ein Beispiel ist im Skript SCRIPTS/fill_file_array.sh zu finden, das die Sprache Python nutzt, um die Strings am NUL-Byte in Wörter zu zerlegen.
Beispiele:
ls -ld $(echo '/etc/pass*' '/boot/*') # Die Kommando-Substitution liefert die beiden Wörter /etc/pass* und # /boot/*. Diese unterliegen der Pfadnamens-Expandierung und fungieren somit als # Muster. An das Kommando ls werden daher alle Dateinamen als separate Wörter # übergeben, die auf die beiden Muster (Wörter) passen. a='x y z' for i in $a do echo "$i" done # Der Inhalt der Variablen a wird in einzelne Wörter zerlegt. # Konkret sind dies hier drei: # Wort 1: x # Wort 2: y # Wort 3: z # An die for-Schleife wird also die aus diesen 3 Wörtern bestehende # (und somit dreielementige) Liste x y z übergeben, weswegen der # Schleifenkörper 3 Mal durchlaufen wird. # Wenn man die Aufspaltung in Wörter durch Quotierung verhindert, # wird an die for-Schleife nur eine einelementige Liste übergeben: for i in "$a" do echo "$i" done # Die Liste besteht nur aus dem Element x y z. # Der Schleifenkörper wird daher nur 1 Mal durchlaufen. # Nachfolgend soll die Wirkung von IFS genauer demonstriert werden. Dabei # nutzen wir die an anderen Stelle definierte Funktion show_args. # drei Variablen mit verschiedenen Strings initialisieren, die anschließend # in Wörter zerlegt werden a=$'\n\n\t\t 1 2:3 ; 4 \t\t\n\n' b=$'\t\t 1 \t\t' c=$' \t\t1\t\t ' # Wir unterstellen zunächst, dass IFS seinen Vorzugs-Wert hat. echo 'Aufspaltung $a ; Vorzugs-Wert IFS' ; show_args $a unset IFS ; echo 'Aufspaltung $a ; IFS nicht existent' ; show_args $a # Diese beiden vorangehenden Zeilen realisieren eine identische Aufspaltung # in Wörter: # Wort 1: 1 # Wort 2: 2:3 # Wort 3: ; # Wort 4: 4 IFS=$'' ; echo 'Aufspaltung $a ; IFS leer' ; show_args $a # Hier unterbleibt die Aufspaltung in Wörter: # Wort 1: $'\n\n\t\t 1 2:3 ; 4 \t\t\n\n' (also $a) IFS=$'\t :;' ; echo 'Aufspaltung $a ; IFS: \t Leerzeichen : ;' ; show_args $a # Newline ($'\n') ist hier ein normales Zeichen: # Wort 1: $'\n\n' # Wort 2: 1 # Wort 3: 2 # Wort 4: 3 # Wort 5: 4 # Wort 6: $'\n\n' IFS=$'\n' ; echo 'Aufspaltung $a ; IFS: \n' ; show_args $a # Nur Folgen von Newlines ($'\n') sind hier Trenner: # Wort 1: $'\t\t 1 2:3 ; 4 \t\t' IFS=$'\t' ; echo 'Aufspaltung $b ; IFS: \t' ; show_args $b # Nur Folgen von Tabulatoren ($'\t') sind hier Trenner: # Wort 1: $' 1 ' IFS=$' ' ; echo 'Aufspaltung $c ; IFS: ' ; show_args $c # Nur Folgen von Leerzeichen sind hier Trenner: # Wort 1: $'\t\t1\t\t'
Sofern die set-Option -f (bzw. noglob) nicht aktiv ist, erfolgt nach der Aufspaltung in Wörter die Pfadnamens-Expandierung. Dabei sucht die Bash in jedem Wort nach den Zeichen
* ? [
Wenn eines dieser Zeichen vorkommt, wird das Wort als Muster betrachtet und (sofern möglich) durch die alphabetisch sortierte Liste aller Datei- bzw. Pfadnamen ersetzt, die auf das Muster passen.
Falls kein Pfadname auf das Muster passt, hängt das Expandierungs-Ergebnis von den shopt-Optionen failglob und nullglob ab. Wenn failglob aktiv ist, erfolgt eine Fehlermeldung der Bash. Das betreffende Kommando wird hier nicht ausgeführt. Wenn failglob nicht aktiv ist, wird die Option nullglob herangezogen. Ist diese aktiv, wird das Wort (Muster) entfernt. Anderenfalls bleibt es unverändert erhalten.
Wenn die shopt-Option nocaseglob aktiv ist, wird bei der Bestimmung der zum Muster passenden Pfadnamen keine Unterscheidung zwischen Groß- und Kleinbuchstaben vorgenommen. Somit würde z.B. das Muster a* auf Pfadnamen passen, die mit einem kleinen a oder großen A beginnen. Diese Einstellung ist besonders in Windows-Umgebungen nützlich, weil dort ja bzgl. Pfadnamen im allgemeinen nicht zwischen Klein- und Großbuchstaben unterschieden wird.
Wenn die shopt-Option dotglob nicht aktiv ist, dann muss in einem Muster ein Punkt (.), der am Anfang eines Pfadnamens oder unmittelbar hinter einem Schrägstrich steht, explizit angegeben werden. Dies ist nicht nötig, wenn entweder der Punkt an anderen als den genannten Stellen steht oder die Option dotglob aktiv ist. Der als Trenner zwischen Pfad-Elementen genutzte Schrägstrich ist generell explizit in einem Muster anzugeben. Beides soll an folgenden Beispielen verdeutlicht werden:
# Ausgabe aller Dateinamen im Verzeichnis /tmp shopt -s dotglob echo /tmp/* # bei gesetzter Option dotglob werden die mit einem Punkt # beginnenden Pfadnamen vom Muster * mit eingeschlossen, nicht # aber die beiden Spezial-Einträge . und .. shopt -u dotglob echo /tmp/{*,.*} # bei deaktivierter Option dotglob werden die mit einem Punkt # beginnenden Pfadnamen vom Muster * nicht mit eingeschlossen, # weswegen wir .* (im Rahmen einer Brace-Expandierung) explizit # angeben, wodurch aber auch die die beiden Spezial-Einträge . und # .. eingeschlossen sind # alternative Realisierung: zuerst nach /tmp wechseln; # dann benötigen wir statt absoluter Pfadnamen nur relative Pfadnamen # und somit keinen Schrägstrich cd /tmp shopt -s dotglob echo * shopt -u dotglob echo * .* # Die folgenden Pfadnamens-Muster expandieren nur bei aktivierter Option # dotglob zum Dateinamen ~/.bashrc: ~/?bashrc ~/*bashrc ~/[.]bashrc ~/[[:punct:]]bashrc # Wenn dotglob deaktiviert ist, erfolgt die Expandierung nur bei # expliziter Angabe des Punktes, z.B. ~/.bash* ~/.bash*c ~/.bash??
Mit der Shell-Variablen GLOBIGNORE kann man die Menge der auf ein Muster passenden Pfadnamen einschränken. Der Inhalt von GLOBIGNORE wird als Folge von Mustern interpretiert, die durch je einen Doppelpunkt voneinander getrennt sind. Die Pfadnamens-Expandierung liefert dann nur jene Pfadnamen, die nicht auf eines der Muster von GLOBIGNORE passen. Anders gesagt: Pfadnamen, die auf ein Muster von GLOBIGNORE passen, werden aus der Ergebnis-Liste der Pfadnamens-Expandierung gestrichen.
Wenn GLOBIGNORE einen nicht leeren Wert hat, dann werden die Pfadnamen . und .. generell ignoriert. Durch die Zuweisung eines nicht leeren Wertes an GLOBIGNORE wird automatisch die Option dotglob gesetzt. Diese Option wird wieder automatisch deaktiviert, wenn man GLOBIGNORE mit unset löscht.
Zur Erläuterung soll das folgende Beispiel dienen, bei dem wir davon ausgehen, dass anfangs die Variable GLOBIGNORE nicht existiert:
shopt -u dotglob mkdir neu cd neu touch .a .b ax ay echo * # Ergebnis der Expandierung: ax ay # Da dotglob deaktiviert ist, werden die mit Punkt beginnenden # Pfadnamen nicht expandiert. GLOBIGNORE='*x' echo * # Ergebnis der Expandierung: .a ay .b # Da dotglob automatisch durch Setzen von GLOBIGNORE # aktiviert wurde, werden die mit Punkt beginnenden Pfadnamen ebenfalls # expandiert. Der Name ax wird allerdings aus der Ergebnis-Liste gestrichen, # weil er zum Muster *x von GLOBIGNORE passt. shopt -u dotglob echo * # Ergebnis der Expandierung: ay # Da wir dotglob explizit wieder deaktiviert haben, werden nun die # mit Punkt beginnenden Pfadnamen nicht mehr generiert, da der Punkt ja # nicht explizit im Muster vorkommt. GLOBIGNORE='*x:.*' echo * # Ergebnis der Expandierung: ay # Durch erneutes Setzen von GLOBIGNORE wird dotglob zwar # wieder aktiviert. Da aber nun das Muster .* zu GLOBIGNORE # gehört, wird die Wirkung von dotglob über diesen Weg wieder # aufgehoben. Das Beispiel zeigt die Trennung zweier GLOBIGNORE-Muster # durch einen Doppelpunkt. unset GLOBIGNORE echo * # Ergebnis der Expandierung: ax ay # Durch Löschen von GLOBIGNORE wird dotglob deaktiviert. # Somit haben wir wieder die Anfangssituation unseres Beispiels # hergestellt.
Die in den Pfadnamens-Mustern nutzbaren Metazeichen werden im Abschnitt Metazeichen für Pfadnamens-Muster detaillierter besprochen.
Entfernung der Quotierungs-Zeichen
Nach der Ausführung aller o.g. Expandierungen entfernt die Bash alle unquotierten Quotierungs-Zeichen (also Backslash, Apostroph und Doppelapostroph), die nicht aus einer der vorangehenden Expandierungen stammen.
Zur Erläuterung dient das folgende Beispiel, das auch wieder die Funktion show_args verwendet:
spec_chars=\\\'\"
show_args \a 'a b' "x z" $spec_chars
# Die Quotierungs-Zeichen in den Argumenten von show_args resultieren aus
# keiner Expandierung und sind selbst nicht quotiert. Also werden sie
# von der Bash entfernt, bevor sie die 4 Argumente an das Kommando
# show_args übergibt:
# Argument 1: a
# Argument 2: a b
# Argument 3: x z
# Argument 4: \'"
# Da die drei Quotierungs-Zeichen des Arguments 4 aus einer Expandierung stammen,
# bleiben sie natürlich erhalten.
Die Bash beherrscht die arithmetische Evaluierung, also die Auswertung arithmetischer Ausdrücke. Sie unterstützt keine Gleitpunkt-Operationen, sondern nur eine Ganzzahl-Arithmetik (Integer-Arithmetik) fester Länge ohne Überlauf-Kontrolle. Eine Division durch Null wird allerdings abgefangen und als Fehler gemeldet. Bei den Interger-Werten handelt es sich um vorzeichenbehaftete 64-Bit-Werte in Zweierkomplement-Darstellung.
Bash bietet dieselben arithmetischen Operatoren mit demselben Vorrang und derselben Assoziativität wie die Programmiersprache C. Zusätzlich steht mit ** ein Potenzierungs-Operator zur Verfügung:
Hier ein kurzer Überblick über die Operatoren (fallend nach Vorrang geordnet):
id++ id-- | Post-Inkrement and -Dekrement von Variablen |
++id --id | Prä-Inkrement and -Dekrement von Variablen |
- + | unäres Minus und Plus |
! ~ | logische und bitweise Negation |
** | Exponentiation |
* / % | Multiplikation, Division, Divisionsrest (Modulo-Division) |
+ - | Addition, Subtraktion |
<< >> | bitweise Links- bzw. Rechtsverschiebung |
<= >= < > | Vergleiche |
== != | Test auf Gleichheit und Ungleichheit |
& | bitweises UND |
^ | bitweises XOR (Exklusiv-ODER) |
| | bitweises ODER |
&& | logisches UND |
|| | logisches ODER |
Ausdruck ? Ausdruck : Ausdruck | ternärer Operator (Bedingungstest) |
= *= /= %= += -= <<= >>= &= ^= |= | Zuweisungen |
Ausdruck1 , Ausdruck2 | Komma-Operator |
Operatoren werden entsprechend ihrem Vorrang ausgewertet. Durch runde Klammern kann man den Vorrang und damit die Auswertungs-Reihenfolge ändern, da geklammerte Teilausdrücke zuerst ausgewertet werden.
Shell-Variablen sind als Operanden zulässig. Vor der Auswertung eines Ausdruck wird eine Parameter-Substitution durchgeführt. Innerhalb eines Ausdrucks kann auf den Wert einer Variablen Bezug genommen werden, indem man deren Namen angibt, wobei das vorangehende Dollar-Zeichen (das normalerweise für eine Parameter-Substitution benötigt wird) entfallen kann. Eine leere oder ungesetzte (nicht existente) Variable wird zu 0 evaluiert.
Wie weiter oben schon erwähnt wurde, kann man eine Variable durch das Kommando
declare i Variablenname
explizit als Integer-Variable deklarieren. Dies hat zur Folge, dass alle Wertzuweisung automatisch arithmetisch evaluiert werden. Um den Wert einer Variablen in einem arithmetischen Ausdruck verwenden zu können, ist es allerdings nicht nötig, die Variable als Integer-Variable zu deklarieren.
Für Integer-Konstanten wird die Syntax
[Basis#]Zahl
verwendet. Die Angabe der Basis ist optional. Wird sie weggelassen, nimmt die Bash automatisch die Basis 10 an.
Konstanten, die mit dem Präfix 0 beginnen, werden als Oktalzahlen interpretiert. Die Präfixe 0x und 0X kennzeichnen Hexadezimal-Zahlen.
Als Basis sind die natürlichen Zahlen von 2 bis 64 zulässig. Die Ziffern größer als 9 werden der Reihe nach durch Kleinbuchstaben, Großbuchstaben sowie die Zeichen @ und _ repräsentiert. Wenn die Basis kleiner als 37 ist, können wahlweise Klein- und Großbuchstaben verwendet werden, um die Ziffern zwischen 10 und 35 darzustellen.
Eine arithmetische Evaluierung erfolgt in folgenden Situationen:
im Rahmen einer arithmetischen Expandierung: $((Ausdruck))
beim eingebauten Kommando let:
let Ausdruck [Ausdruck ...]
bei der Wertzuweisung an eine explizit deklarierte Integer-Variable.
Die einzelnen Ausdrücke von let müssen aus Sicht der Shell jeweils separate Wörter sein. Ggf. muss man sie also geeignet quotieren. Der Exit-Status von let ist 1, falls der letzte Ausdruck den Wert Null liefert, sonst 0.
Die arithmetische Ausdrucks-Auswertung mit
((Ausdruck))
entspricht folgendem let-Kommando:
let "Ausdruck"
Daher kann man innerhalb des arithmetischen Ausdrucks auch eine Kommando-Substitution nutzen.
Beispiele:
echo $((9#11 + 10)) # 10+10 --> Ausgabe: 20 a=9 echo $((a--)) $((--a)) # Ausgabe: 9 7 ((a == 7)) && echo OK # Ausgabe: OK, da a den Wert 7 hat (($a == 7)) && echo OK # dito; hier mit $ vor dem Variablenamen let a=9+10 a=a+1 # $? hat den Wert 0, a den Wert 20 declare -i a # a als Integer-Variable deklarieren a=a**2 # a ergibt sich zu a hoch 2 (400) # Nutzung der Kommando-Substitution: ((b = `echo 1` + 1)) # b bekommt den Wert 2 ((b = $(echo 1) + 1)) # dito # Durch den Komma-Operator kann man mehrere Ausdrücke syntaktisch zu einem # einzigen Ausdruck zusammenfassen. Das folgende Beispiel zeigt dies: ((a=9, b=10)) # Hier wird also der Variablen a die Zahl 9 und der Variablen b # die Zahl 10 zugewiesen. Dieses Konstrukt entspricht folgendem let-Kommando: let "a=9, b=10" # Man kann bei let natürlich auch den einfachen Apostroph sowie den # Backslash zum Quotieren benutzen: let 'a=9, b=10' let a=9,\ b=10 # Bei Verzicht auf das Leerzeichen nach dem Komma-Operator ist gar keine # Quotierung nötig, da die Shell dann genau ein Argument an let übergibt: let a=9,b=10 # An das Kommando let kann man die beiden Zuweisungen als separate # Ausdrücke übergeben, was inhaltlich denselben Effekt hätte. # Der Komma-Operator ist hier also nicht nötig: let a=9 b=10
Bedingungs-Ausdrücke (conditional expressions) werden durch das zusammengesetzte Kommando [[ sowie durch die beiden funktionell identischen eingebauten Kommandos test und [ genutzt, um Datei-Attribute zu testen sowie arithmetische und String-Vergleiche durchzuführen.
Die Bash bietet die in der folgenden Tabelle genannten unären und binären Test-Operaten (primaries) zur Bildung von Bedingungs-Ausdrücken an. Wenn ein Datei-Argument dieser Operatoren die Form /dev/fd/n hat, dann wird der Datei-Deskriptor n getestet. Falls das Datei-Argument /dev/stdin, /dev/stdout oder /dev/stderr lautet, dann wird der korrespondierende Deskriptor (also 0, 1 oder 2) getestet.
-a Datei | wahr, wenn Datei existiert | |||||||||||||||||||||
-b Datei | wahr, wenn Datei existiert und eine blockorientierte Gerätedatei ist | |||||||||||||||||||||
-c Datei | wahr, wenn Datei existiert und eine zeichenorientierte Gerätedatei ist | |||||||||||||||||||||
-d Datei | wahr, wenn Datei existiert und ein Verzeichnis (Directory) ist | |||||||||||||||||||||
-e Datei | wahr, wenn Datei existiert | |||||||||||||||||||||
-f Datei | wahr, wenn Datei existiert und eine reguläre Datei ist | |||||||||||||||||||||
-g Datei | wahr, wenn Datei existiert und ihr Set-Group-ID-Bit gesetzt ist | |||||||||||||||||||||
-h Datei | wahr, wenn Datei existiert und symbolischer Link ist | |||||||||||||||||||||
-k Datei | wahr, wenn Datei existiert und ihr Sticky-Bit gesetzt ist | |||||||||||||||||||||
-p Datei | wahr, wenn Datei existiert und sie eine benannte Pipe (FIFO) ist | |||||||||||||||||||||
-r Datei | wahr, wenn Datei existiert und lesbar ist | |||||||||||||||||||||
-s Datei | wahr, wenn Datei existiert und eine Länge größer 0 hat | |||||||||||||||||||||
-t fd | wahr, wenn Datei-Deskriptor fd geöffnet ist und sich auf ein Terminal bezieht | |||||||||||||||||||||
-u Datei | wahr, wenn Datei existiert und ihr Set-User-ID-Bit gesetzt ist | |||||||||||||||||||||
-w Datei | wahr, wenn Datei existiert und schreibbar ist | |||||||||||||||||||||
-x Datei | wahr, wenn Datei existiert und ausführbar ist | |||||||||||||||||||||
-O Datei | wahr, wenn Datei existiert und ihr Eigentümer der effektiven User-ID des aktuellen Shell-Prozesses entspricht | |||||||||||||||||||||
-G Datei | wahr, wenn Datei existiert und ihre Gruppe der effektiven Gruppen-ID des aktuellen Shell-Prozesses entspricht | |||||||||||||||||||||
-L Datei | wahr, wenn Datei existiert und ein symbolischer Link ist | |||||||||||||||||||||
-S Datei | wahr, wenn Datei existiert und ein Socket ist | |||||||||||||||||||||
-N Datei | wahr, wenn Datei existiert und seit dem letzten Lesen modifiziert wurde (die Zugriffszeit liegt vor der Modifikationszeit) | |||||||||||||||||||||
Datei1 -nt Datei2 | wahr, wenn Datei1 (gemäß der Modifikationszeit) neuer ist als Datei2 oder wenn Datei1 existiert und Datei2 nicht | |||||||||||||||||||||
Datei1 -ot Datei2 | wahr, wenn Datei1 (gemäß der Modifikationszeit) älter ist als Datei2 oder wenn Datei2 existiert und Datei1 nicht | |||||||||||||||||||||
Datei1 -ef Datei2 | wahr, wenn sich Datei1 und Datei2 auf dieselbe Geräte- und Inode-Nummer beziehen, also identische Files sind | |||||||||||||||||||||
-o Option | wahr, wenn die (mit set einstellbare) Shell-Option Option aktiv ist | |||||||||||||||||||||
-z String | wahr, wenn String die Länge 0 hat | |||||||||||||||||||||
-n String | wahr, wenn String eine Länge größer 0 hat | |||||||||||||||||||||
String | identisch mit -n String | |||||||||||||||||||||
String1 == String2 | wahr, wenn die beiden Strings gleich sind
Bei strikter POSIX-Konformität muss man = statt == verwenden. | |||||||||||||||||||||
String1 != String2 | wahr, wenn die beiden Strings ungleich sind | |||||||||||||||||||||
String1 < String2 | wahr, wenn String1 in der lexikografischen Ordnung der aktuellen Locale vor String2 steht | |||||||||||||||||||||
String1 > String2 | wahr, wenn String1 in der lexikografischen Ordnung der aktuellen Locale hinter String2 steht | |||||||||||||||||||||
Argument1 Operator Argument2 | wahr, wenn der arithmetische Vergleich der beiden ganzzahligen Operanden
Argument1 und Argument2 unter Verwendung des binären Operators
Operator wahr ist
Die folgende Tabelle nennt die möglichen Operatoren und deren Bedeutung:
Beispiele: -1 -eq 17 45 -lt 90 |
Unter einer Pipe oder Pipeline versteht man eine Folge von mindestens einem (einfachen oder zusammengesetzten) Kommando, die durch das Pipe-Zeichen | getrennt sind.
Syntax einer Pipeline:
[time [-p]] [ ! ] Kommando [ | Kommando2 ... ]
Die Standard-Ausgabe (stdout) von Kommando wird dabei über eine Pipe mit der Standard-Eingabe (stdin) von Kommando2 verbunden.
Der Exit-Status der Pipe entspricht standardmäßig dem Exit-Status des letzten Pipe-Kommandos. Das Ausrufezeichen (!) vor der Pipe bewirkt die logische Negation des Exit-Status.
Das reservierte Wort time vor einer Pipeline bewirkt, dass nach der Beendigung der Pipe deren Laufzeit gemeldet wird. Die Option -p von time verändert nur die Darstellung der Zeitangaben, indem sie das POSIX-Format aktiviert.
Die Bash wartet, bis alle Kommandos der Pipe beendet wurden, bevor sie einen Status meldet.
Über das Feld PIPESTATUS können Pipe-Fehler erkannt werden, da der Exit-Status jedes Kommandos der jeweils zuletzt ausgeführten Pipe als Element dieses Feldes vermerkt wird. Ab Bash 3.0 wird zusätzlich die Option pipefail unterstützt. Wenn sie gesetzt ist, entspricht der Pipe-Status
Beispiele:
# Zeilenanzahl der Ausgabe von ls -l ermitteln ls -l | wc -l # Exit-Status anzeigen echo $? # obiges ls-Kommando wiederholen, aber Exit-Status negieren ! !l # Da dem ersten ! ein Leerzeichen folgt, wird hier keine # History-Expandierung durchgeführt. Das zweite Wort ist # dagegen ein gültiger History-Ausdruck, der entsprechend # expandiert wird. # Status-Anzeige (Kommando echo) wiederholen !e # Das Kommando tee fügt ein T-Stück in die Pipe ein, d.h., # tee dupliziert die von stdin gelesenen Daten. Eine Kopie wird nach # stdout und in jede der als Argument benannten Dateien (im Beispiel # nur in die Datei /tmp/ls.txt) ausgegeben. ls -l | tee /tmp/ls.txt | wc -l ls -l | tee /tmp/ls.txt | wc -l > /tmp/wcl.txt # Anmerkung: Die zsh beherrscht ein implizites tee. Man # kann die Standard-Ausgabe eines Kommandos gleichzeitig in # mehrere Dateien und ggf. noch zusätzlich in eine Pipe # umlenken: ls -l > /tmp/ls.txt | wc -l ls -l > /tmp/ls.txt > /tmp/ls2.txt | wc -l # allen Verzeichnissen unterhalb des aktuellen Verzeichnisses (.) für # die Gruppe und die Welt (go) das Ausführungs-Recht (+x) setzen find . -type d | xargs chmod go+x # Zeitmessung der Berechnung der MD5-Summen aller Dateien # im Verzeichnis /boot time md5sum /boot/* >/dev/null 2>/dev/null # Verzeichnisse werden von md5sum ignoriert. # Die Ausgaben des Kommandos werden durch Umlenkung nach /dev/null # vernichtet. Daher sehen wir nur die Zeitangaben. # Alternative (if) unter Verwendung der Negation des Exit-Status # des Kommandos cd if ! cd /etc 2> /dev/null then echo cd schlug fehl exit 1 fi # Pipe starten, deren mittleres Kommando fehlschlägt true | false | true # Pipe-Status anzeigen echo ${PIPESTATUS[*]}
Eine Fehlerkontrolle für Pipes lässt sich z.B. über eine Funktion check_pipe_error zentralisieren, die man in einer separaten Datei ablegen und dann per source einbinden kann:
# Datei check_pipe_error.sh # interne Funktion zur Überprüfung des PIPESTATUS # $1 --> PIPESTATUS # $2 --> optional; falls "0", dann Fehlermeldung unterdrücken function check_pipe_error_intern() { local i=1 rc for rc in $1 do if ((rc)) then [[ $2 != 0 ]] && echo "Fehler bei Pipe-Kommando $i (Rueckgabewert: $rc)" return 1 fi ((i++)) done return 0 } # Überprüfung des PIPESTATUS, um einen Fehler in einer Pipe aufzuspüren function check_pipe_error() { check_pipe_error_intern "${PIPESTATUS[*]}" $1 }
Nutzung in einem anderen Skript:
source check_pipe_error.sh datenbank_abfrage | awk -f filter.awk | sort > resultat.txt # Wir unterstellen hier, dass das Kommando datenbank_abfrage # eine Datenbank-Abfrage realisiert, seine Daten über ein Pipe an ein # AWK-Filter-Programm übergibt und dessen Ergebnis über eine weitere Pipe # an das Kommando sort weiterleitet, um am Ende gefilterte und sortierte # Abfrage-Ergebnisse zu erhalten. # Wichtig!!! Die Funktion check_pipe_error muss unmittelbar nach der # zu prüfenden Pipe gerufen werden, um eine ungewollte Veränderung des # Inhalts von PIPESTATUS zu vermeiden. check_pipe_error || exit 1 # ohne Fehlermeldung in check_pipe_error: # check_pipe_error 0 || exit 1 echo Abfrage OK
Unter einer Liste versteht die Bash folgendes:
; & && ||
; & Newline
Innerhalb einer Liste kann statt eines Semikolons auch eine Folge von Newline-Zeichen als Trenner zwischen den Kommandos (Pipelines) fungieren.
Kommandos, die durch ein Semikolon oder eine Newline-Folge voneinander getrennt sind, werden streng sequentiell ausgeführt. Die Shell wartet also immer das Ende eines Kommandos ab, bevor sie das nächste Kommando interpretiert und ausführt. Der Exit-Status einer solchen Liste entspricht dem Exit-Status des letzten ausgeführten Kommandos der Liste.
Beispiel:
# die Kommandos date und uptime sequentiell ausgeführen date ; uptime # Alternativ-Notation mit Newline statt Semikolon: date uptime
Mit dem Operator && wird eine UND-Liste gebildet. Das Konstrukt
Kommando1 && Kommando2
ist eine Kurzform der folgenden unvollständigen Alternative:
if Kommando1 then Kommando2 fi
Kommando2 wird also nur dann ausgeführt, wenn Kommando1 erfolgreich (also mit dem Exit-Status 0) beendet wurde.
Beispiel:
# die Datei /etc/passwd ausgeben, wenn sie lesbar ist
[[ -r /etc/passwd ]] && cat /etc/passwd
Der Exit-Status einer UND-Liste entspricht dem Exit-Status des letzten ausgeführten Kommandos der Liste.
Mit dem Operator || wird eine ODER-Liste gebildet. Das Konstrukt
Kommando1 || Kommando2
ist eine Kurzform der folgenden unvollständigen Alternative mit negierter Bedingung:
if ! Kommando1 then Kommando2 fi
Kommando2 wird also nur dann ausgeführt, wenn Kommando1 mit einem Fehler (also einem Exit-Status ungleich 0) beendet wurde.
Beispiel:
# eine Meldung ausgeben, wenn die Datei /etc/shadow nicht lesbar ist
[[ -r /etc/shadow ]] || echo '/etc/shadow nicht lesbar'
Der Exit-Status einer ODER-Liste entspricht dem Exit-Status des letzten ausgeführten Kommandos der Liste.
Hinweis: Da die Kommandos der mit den Operatoren
; && || Newline
gebildeten Listen sequentiell ausgeführt werden, können Dateien, die von einem Kommando der Liste neu erzeugt wurden, in einem nachfolgenden Kommando über die Pfadnamens-Expandierung gefunden werden, da das Nachfolge-Kommando erst nach dem Abschluss des vorangehenden Kommandos interpretiert und ausgeführt wird. Das folgende Beispiel soll dies verdeutlichen:
rm -f /tmp/x*
touch /tmp/xyz && echo /tmp/x*
# Das Kommando rm löscht alle Dateien im Verzeichnis /tmp, deren
# Name mit x beginnt. Durch touch wird die Datei /tmp/xyz erzeugt.
# Die Pfadnamens-Expandierung des zweiten Kommandos der Liste (echo)
# findet dann die Datei /tmp/xyz.
Wenn hinter einem Kommando der Operator & folgt, dann führt die Shell dieses Kommando im Hintergrund, d.h. in einer Sub-Shell (also einem Kind-Prozess der aktuellen Shell) aus, auf deren Ende die Shell nicht wartet. Sie kehrt sofort mit dem Exit-Status 0 zurück. Hintergrund-Kommandos werden daher auch als asynchrone Kommandos bezeichnet. Alle sonstigen Kommandos sind synchrone Kommandos, da sie von der Shell im Vordergrund, d.h. im aktuellen Shell-Prozess ausgeführt werden und die Shell auf deren Ende wartet, bevor sie das Folge-Kommando ausführt.
Das Beispiel-Skript bg.sh soll unten im Hintergrund ausgeführt werden. Es hat folgenden Inhalt:
#!/bin/sh # # hängt aller 3 Sekunden einen Zeitstempel an /tmp/bg_out.txt an # den Datei-Namen an die Variable file zuweisen file=/tmp/bg_out.txt # Signal-Handler für die ersten 15 Signale installieren (bei Signal 9 # geht das zwar nicht, aber man kann es problemlos angeben) for (( i=1 ; i < 16 ; i++ )) do trap "echo Signal $i ; exit 1" $i done echo "Los geht's" # Datei löschen rm -f $file # Endlos-Schleife while ((1)) do # Datum an die Datei anhängen date >> $file # 3 Sekunden schlafen sleep 3 done
Das Skript wird nun im Hintergrund gestartet:
./bg.sh &
Mit dem Kommando
tail -f /tmp/bg_out.txt
lassen wir uns das Wachstum des Inhalts der vom Hintergrund-Skript periodisch fortgeschriebenen Datei /tmp/bg_out.txt anzeigen, um die Arbeit des Skripts zu kontrollieren.
Wenn ein im Hintergrund und damit in einer Sub-Shell ausgeführtes Kommando seine Ausführungs-Umgebung durch Variablenzuweisungen oder eingebaute Kommandos (z.B. Verzeichnis-Wechsel oder Änderung der Signalbehandlung) modifiziert, so wirken diese Änderungen nicht auf die aktuelle Shell (den Eltern-Prozess) zurück. Daher ändert z.B. das Kommando
a=9 && cd /tmp &
weder den Wert der Variablen a noch das aktuelle Verzeichnis der aktuellen Shell, da die gesamte UND-Liste im Hintergrund ausgeführt wird.
Die Prozess-Nummer des letzten Hintergrund-Prozesses steht im Parameter $! zur Verfügung. Sie kann man verwenden, um dem Hintergrund-Prozess mit dem Kommando kill ein Signal zu senden oder um mit dem Kommando wait auf dessen Beendigung zu warten. Neben Prozess-Nummern akzeptieren diese beiden Kommandos auch Job-Spezifikationen (s. Job-Steuerung). Mit den Mitteln der Job-Steuerung lassen sich Hintergrund-Prozesse auch wieder in den Vordergrund holen und Vordergrund-Prozesse in den Hintergrund verlagern.
Das Kommando
wait [n]
wartet auf die Beendigung des über seine Job-Spezifikation oder Prozess-ID spezifizierten Jobs oder Prozesses und gibt dessen Exit-Status zurück. Wenn man eine Job-Spezifikation als Argument angibt, wird auf das Ende aller Prozesse der Pipeline gewartet, die den Job bildet.
Wenn das optionale Argument n fehlt, wartet wait auf alle derzeit aktiven Kind-Prozesse. Der Exit-Status ist dann 0. Wenn n einen nicht existenten Prozess bezeichnet, lautet der Exit-Status 127. Ansonsten entspricht er dem Exit-Status des letzten Jobs oder Prozesses, auf den gewartet wurde.
Im folgenden Beispiel starten wir ein Kommando im Hintergrund. Anschließend werden parallel zum Hintergrund-Kommando weitere Kommandos des aktuellen Skripts ausgeführt. Mit wait wird danach auf das Ende des Hintergrund-Kommandos gewartet:
# Hintergrund-Kommando starten sleep 5 && echo Hintergrund & # Prozess-ID des Hintergrund-Kommandos merken pid=$! # eine Schleife 3 Mal durchlaufen for ((i = 1; i < 4; i++)) do echo "Vordergrund $i" sleep 1 done # auf das Ende des Hintergrund-Kommandos warten wait $pid # danach weiter im Vordergrund echo 'Hintergrund-Kommando beendet'
Da wir in diesem Beispiel nur genau ein Hintergrund-Kommando gestartet haben, könnten wir hier statt
wait $pid
auch das argumentlose
wait
verwenden, um auf alle Hintergrund-Kommandos zu warten. Die Angabe einer Prozess-ID oder Job-Spezifikation ist nur dann nötig, wenn man mehrere Hintergrund-Kommandos bzw. -Jobs hat und selektiv auf das Ende eines bestimmten Prozesses oder Jobs warten will.
Statt der Prozess-ID $pid hätten wir auch eine Job-Spezifikation angeben können, z.B. %1 für Job 1, %+ für den aktuellen Job oder %sleep für den Job, der mit sleep beginnt.
Anmerkung: Die Ermittlung einer Job-Nummer ist evtl. aufwendiger als die Ermittlung der Prozess-ID, da es keinen zu !$ analogen Parameter gibt, der diese Nummer bereitstellt. Wenn man allerdings nur einen Job hat, weiß man sicher, dass er die Nummer 1 trägt.
Abschließend wollen wir noch den Vorrang der Listen-Operatoren diskutieren, der dann von Bedeutung ist, wenn man Listen unter Verwendung verschiedener Operatoren konstruiert.
Die Operatoren && und || haben denselben Vorrang. Dieser ist höher als der Vorrang der Operatoren ; und &, wobei auch diese beiden denselben Vorrang haben.
Durch die Verwendung von runden oder geschweiften Klammern, mit denen man zusammengesetzte Kommandos bilden kann, lässt sich der Vorrang ändern.
Die folgenden Beispiele sollen dies verdeutlichen:
sleep 5 ; echo 1 & # Da ; und & denselben Vorrang haben, bezieht sich & nur auf das Kommando # echo. Dieses wird im Hintergrund ausgeführt, nachdem mit sleep 5 # Sekunden gewartet worden war. { sleep 5 ; echo 1 ; } & # Hier wird die gesamte Kommando-Liste in den Hintergrund verlagert. Dort wird # dann 5 Sekunden gewartet und anschließend eine 1 ausgegeben. sleep 5 && echo 1 & # Die Liste sleep 5 && echo 1 wird durch & im Hintergrund ausgeführt. # Da && einen höheren Vorrang als & hat, bezieht sich & auf die gesamte Liste. # Mit sleep wird hier 5 Sekunden gewartet. Nach der erfolgreichen # Beendigung dieses ersten Kommandos wird dann mit echo eine 1 ausgegeben. sleep 5 && { echo 1 & } # Hier bezieht sich & nur auf das Kommando echo, da die geschweiften # Klammern ein zusammengesetztes Kommando bilden, das eine Liste in der # aktuellen Shell ausführt. Nach der erfolgreichen Beendigung von sleep # wird daher echo 1 im Hintergrund ausgeführt. sleep 5 && ( echo 1 & ) # Im Unterschied zum vorigen Beispiel verwenden wir hier runde statt geschweifter # Klammern. Dadurch wird eine Liste in einer Sub-Shell statt in der aktuellen Shell # ausgeführt, was aber hier keinen funktionellen Unterschied zur Folge hat. ! sleep 5 || ( echo 1 & ) # Hier wird die Funktionalität des vorigen Beispiels durch eine ODER- # statt einer UND-Liste erbracht. Daher wurde der Exit-Status des ersten # Kommandos negiert. true && false || echo falsch && echo richtig # Die Ausgabe lautet: # falsch # richtig # Da && und || denselben Vorrang haben, wird zuerst die linke UND-Liste # true && false ausgeführt. Sie hat den Exit-Status 1. Diese UND-Liste ist das # erste Kommando einer ODER-Liste. Deren zweites Kommando, also echo falsch, wird # ausgeführt, weil das erste Kommando mit einem Fehler-Code beendet wurde. # Der Exit-Status der ODER-Liste lautet 0, weil deren zweites Kommando erfolgreich # zurückkehrte. Die gesamte ODER-Liste ist das erste Kommando einer weiteren UND-Liste. # Da die ODER-Liste erfolgreich war, wird das zweite Kommando der letzten UND-Liste, # also echo richtig, ebenfalls ausgeführt. true || false && echo richtig || echo falsch # Die Ausgabe lautet richtig. Die linke ODER-Liste ist erfolgreich. # Daher wird echo richtig als zweites Kommando der UND-Liste ausgeführt. # Diese Liste ist ebenfalls erfolgreich, weswegen das zweite Kommando der # letzten ODER-Liste nicht zur Ausführung gelangt. true || { false && echo richtig || echo falsch ; } # Hier erfolgt keine Ausgabe. Das Gesamt-Konstrukt ist eine ODER-Liste. # Da deren erstes Kommando, also true, erfolgreich ist, wird das # zweite Kommando überhaupt nicht ausgeführt. true && { false && echo richtig || echo falsch ; } # Die Ausgabe lautet falsch. Das Gesamt-Konstrukt ist eine UND-Liste. # Da deren erstes Kommando, also true, erfolgreich ist, wird das # zweite Kommando ausgeführt, bei dem es sich um eine ODER-Liste handelt, # deren erstes Kommando wiederum eine UND-Liste (false && echo richtig) ist. # Die UND-Liste ist nicht erfolgreich, daher kommt echo falsch zur Ausführung.
Die Job-Steuerung gestattet, Prozesse selektiv zu stoppen (suspendieren, suspend) und später wieder weiterlaufen zu lassen (resume). Sie steht in der Bash zur Verfügung, wenn die set-Option m bzw. monitor aktiv ist, was in interaktiven Shells dem Standard entspricht. Typischerweise nutzt man die Job-Steuerung durch eine interaktive Schnittstelle, die durch das Zusammenspiel des Terminal-Treibers mit der Bash realisiert wird.
Jede von der Bash gestartete Pipeline wird als Job betrachtet und in eine Job-Liste eingetragen, die die jeweils aktiven Jobs einer Bash enthält und die man sich mit dem Kommando jobs ausgeben lassen kann. In dieser Ausgabe wird der aktuelle, d.h. zuletzt angehaltene oder im Hintergrund gestartete Job durch ein + und der zuvor aktuelle Job durch ein - gekennzeichnet.
Alle Jobs sind nummeriert und somit über ihre Job-Nummer immer eindeutig identifizierbar. Neben der Nummer gibt es weitere Möglichkeiten der Job-Identifizierung, die wir unten besprechen. Die Job-Nummern werden nicht generell fortlaufend vergeben. Ein Job, der neu in die Job-Liste eingetragen wird, erhält die Nummer 1, wenn die aktuelle Liste leer ist (unabhängig davon, ob sie zuvor schon Jobs enthielt oder nicht). Andernfalls wird ihm die um eins erhöhte höchste Job-Nummer der Liste zugeordnet (z.B. die 8, wenn die bisher höchste Job-Nummer die 7 war), so dass es keine Rolle spielt, ob es unterhalb der höchsten Nummer noch freie Nummern gibt, da sie hier nicht wiederverwendet werden.
Wenn die Bash einen Job im Hintergrund (asynchron) ausführt, also nicht auf seine Beendigung wartet, dann gibt sie eine Zeile der Form
[1] 9876
aus, die anzeigt, dass Job 1 gestartet wurde, wobei der letzte Prozess der Pipeline dieses Jobs die ID 9876 hat.
Die durch Tastatur-Eingaben generierten Signale (z.B. SIGINT, das typischerweise durch Ctrl-C erzeugt wird) werden nur an Prozesse im Vordergrund gesendet. Hintergrund-Prozesse erhalten diese Signale nicht. Nur Vordergrund-Prozesse dürfen vom Terminal lesen. Hintergrund-Prozesse erhalten dagegen das Signal SIGTTIN, wenn sie eine Lese-Operation auf das Terminal anwenden. Dieses Signal stoppt standardmäßig einen Prozess, kann allerdings auch vom Prozess explizit abgefangen werden.
Bei aktiver Terminal-Option tostop ist es einem Hintergrund-Prozess auch nicht erlaubt, auf das Terminal zu schreiben. Er erhält dann beim Schreibversuch das Signal SIGTTOU, welches ihn standardmäßig stoppt, allerdings auch abgefangen werden kann. Diese Option lässt sich mit dem Kommando
stty tostop
setzen und mit
stty -tostop
wieder zurücksetzen.
Wenn das Betriebssystem eine Job-Steuerung unterstützt, wird sie von der Bash genutzt. Wenn der Nutzer das Suspend-Zeichen (typischerweise Ctrl-Z, wobei man mit dem Kommando stty die aktuelle Einstellung ausgeben und auch ein anderes Suspend-Zeichen einstellen kann) eingibt, während ein Vordergrund-Job läuft, wird dieser sofort suspendiert. Die Shell weist darauf mit einer Zeile der Form
[1]+ Stopped Pipeline
hin. Die Zahl in Klammern ist wieder die Job-Nummer. Am Ende der Zeile wird die konkret gestoppte Pipeline angegeben, was z.B. so aussehen kann:
[1]+ Stopped cat | read
Als Nebeneffekt der Suspendierung werden alle noch nicht vom Terminal gelesenen Eingabe-Daten verworfen.
Durch die Suspendierung erhält die Shell die Steuerung zurück. Anschließend kann der Nutzer mittels der Kommandos zur Job-Steuerung den Job-Status manipulieren: Mit bg (background) kann man suspendierte Jobs in den Hintergrund verlagern. Mit fg (foreground) lassen sich suspendierte oder Hintergrund-Prozesse in den Vordergrund holen. Das Kommando kill sendet Signale an Jobs oder Prozesse und das Kommando wait wartet auf die Beendigung eines Jobs oder Prozesses.
Anmerkung: bg und fg unterstützen nur Job-Spezifikationen als Argument, wogegen kill und wait sowohl Job-Spezifikationen als auch Prozess-IDs akzeptieren.
Die Suspendierung eines Jobs erfolgt intern dadurch, dass den Prozessen des Jobs das Signal SIGSTOP gesendet wird. Die oben beschriebene Eingabe des Suspend-Zeichen (typischerweise Ctrl-Z) bewirkt genau das, allerdings kann man damit nur den Vordergrund-Job beeinflussen. Um laufende Hintergrund-Jobs zu stoppen, muss man sie entweder mit fg in den Vordergrund holen und danach mit Ctrl-Z suspendieren oder ihnen mit dem Kommando kill -STOP das SIGSTOP senden.
Anmerkung: Die tcsh sowie die Korn-Shell kennen hierfür das Kommando stop, das bei der tcsh ein eingebautes Kommando und bei der Korn-Shell ein Alias für kill -STOP ist.
Die Bash unterstützt verschiedene Formen von Job-Spezifikationen, wobei generell das Zeichen % eine Job-Spezifikation einleitet:
%n | Job Nummer n |
%+ und %% | der aktuelle Job der Job-Liste (der zuletzt gestoppte Vordergrund-Job bzw. der zuletzt gestartete Hintergrund-Job) |
%- | der vorher aktuelle Job der Job-Liste |
%Name | Name ist ein Präfix des Kommandos, mit dem der Job gestartet wurde. Es muss eindeutig sein, da die Bash sonst einen Fehler meldet. |
%?Teil-String | Teil-String ist ein String, der irgendwo in der Kommandozeile eines Jobs vorkommt, wobei auch dieser (wie oben das Präfix) eindeutig sein muss, da die Bash sonst einen Fehler meldet. |
Wenn man auf einer Kommandozeile nur eine Job-Spezifikation angibt, wird dies von der Bash als Synonym für das Kommando
fg Job-Spezifikation
interpretiert. Man holt als den spezifizierten Job in den Vordergrund und beendet damit automatisch auch eine evtl. Suspendierung. Analog wird
Job-Spezifikation &
als Synonym für
bg Job-Spezifikation
betrachtet, so dass der spezifizierte Job in den Hintergrund verlagert und dadurch seine Suspendierung beendet wird. Intern wird dem Job dabei das Signal SIGCONT gesendet, das man auch mit kill -CONT senden kann.
Wenn die Shell-Variable auto_resume gesetzt ist, werden einfache Kommandos, die nur aus einem Wort bestehen und keine Umlenkungen enthalten, als Kandidaten für die automatische Wiederaufnahme eines gestoppten Jobs betrachtet, wobei hier keine Mehrdeutigkeiten zulässig sind. Das eingegebene Wort (der Wiederaufnahme-Kandidat) aktiviert denjenigen Job, dessen Kommando-Zeile zu diesem Wort passt. Wenn es mehrere passende Jobs gibt, wird der letzte passende der Liste ausgewählt (jener mit der höchsten Job-Nummer).
Wenn auto_resume den Wert exact hat, dann muss das Wort exakt der Kommando-Zeile entsprechen bzw. die Kommando-Zeile als Präfix enthalten. Wenn beispielsweise die Job-Liste die gestoppten Jobs
abcd abc
enthält und abc eine höhere Job-Nummer als abcd hat, dann startet das Wort abcd den Job abc, da er vom Namen her passt (weil abc ein Präfix von abcd ist) und in der Liste hinter abcd steht.
Wenn auto_resume den Wert substring hat, dann muss das Wort ein Teil-String der Kommando-Zeile sein. Dies entspricht der Funktionalität der Job-Spezifikation mit %?String. Alle anderen Werte von auto_resume bewirken, dass das Wort am Anfang der Kommando-Zeile vorkommen muss. Dies entspricht der Funktionalität der Job-Spezifikation mit %String.
Die Shell registiert immer unverzüglich die Status-Änderungen von Jobs. Normalerweise meldet sie solche Änderungen aber erst dann auf der Standard-Ausgabe, wenn sie den nächsten Prompt ausgibt, um andere Ausgaben nicht zu stören. Sofern die set-Option -b aktiv ist, meldet die Bash Job-Status-Änderungen sofort.
Jedes Kind (Prozess eines Jobs) einer Bash, das beendet wird, sendet ein Signal SIGCHLD. Ein mit dem Kommando trap eingerichteter Signal-Handler für SIGCHLD wird dadurch aktiviert. Wenn also z.B. eine aus drei Prozessen bestehende Pipe beendet ist, wurde der Signal-Handler für SIGCHLD drei Mal aufgerufen.
Beim Versuch, eine Bash zu beenden, die noch gestoppte Jobs hat, wird eine Warnung ausgegeben. Wenn man danach sofort wieder das Beenden der Shell anweist, ohne ein anderes Kommando auszuführen, werden die gestoppten Jobs sowie die Shell ohne weitere Warnung beendet.
Beispiele:
# Job-Liste anzeigen # + kennzeichnet den aktuellen (zuletzt angehaltenen # oder im Hintergrund gestarteten) Job # - kennzeichnet den zuvor aktuellen Job jobs # aktuellen Job in den Vordergrund holen fg # Job Nummer 1 in den Vordergrund holen und durch Ctrl-C abbrechen fg %1 Ctrl-C # Job Nummer 2 in den Vordergrund holen und durch Ctrl-Z suspendieren %2 Ctrl-Z # Hintergrund-Job starten ./bg.sh & # den aktuellen Job beenden kill %+ # Vordergrund-Job starten ./bg.sh # Vordergrund-Job suspendieren Ctrl-Z # in den Hintergrund verlagern (hebt die Suspendierung auf) bg # im Hintergrund suspendieren kill -STOP %+ # Suspendierung aufheben und dadurch im Hintergrund weiterlaufen lassen %+& # auf das Ende des Hintergrund-Prozesses warten wait # ohne Argument wird auf das Ende aller Kind-Prozesse gewartet wait %1 # wartet auf das Ende von Job 1 # Mit disown kann man die Job-Liste manipulieren: disown %1 # Job 1 aus der Liste löschen disown # den aktuellen Job aus der Liste löschen disown -r # alle laufenden (running) Jobs aus der Liste löschen disown -a # alle Jobs aus der Liste löschen disown -h %1 # Job 1 so markieren, dass kein SIGHUP mehr an ihn geschickt wird disown -ha # alle Jobs so markieren, dass kein SIGHUP mehr an sie geschickt wird # Nutzung von auto_resume disown -a # Job-Liste löschen unset auto_resume # Variable auto_resume löschen abcd # Kommando abcd starten Ctrl-Z # abcd stoppen abc # Kommando abc starten Ctrl-Z # abc stoppen jobs # Job-Liste anzeigen: # [1]- Stopped abcd # [2]+ Stopped abc auto_resume=exact # Variable auto_resume auf exact setzen abcd # Wiederaunahme des Kommandos abc (nicht abcd) Ctrl-Z # abc stoppen auto_resume=substring # Variable auto_resume auf substring setzen d # Wiederaunahme des Kommandos abcd, da nur dort der Sub-String d vorkommt Ctrl-Z # abcd stoppen auto_resume=x # Variable auto_resume auf beliebigen anderen Wert setzen d # Fehler: es gibt kein mit d beginnendes Kommando: # bash: d: command not found ab # Wiederaunahme des Kommandos abc, weil es mit ab beginnt
Mit disown aus der Job-Liste gelöschte Jobs werden nicht beendet. Ihnen sendet die Shell aber keine Signale mehr, also auch kein Signal SIGHUP, das einen Prozess beendet, wenn er es nicht abfängt. Eine interaktive Shell sendet SIHUP in folgenden Situationen an alle Jobs:
Die Shell empfing selbst ein SIGHUP und beendet sich infolge dessen.
Die Shell wird beendet, während die shopt-Option huponexit gesetzt war.
Mit disown -h kann man gezielt das Senden des SIGHUP verhindern, ohne den Job aus der Job-Liste zu streichen.
Alternativ kann man das externe Kommando nohup verwenden, um einen Prozess so zu starten, dass er gegen SIGHUP immun ist, wobei hier seine Ausgabe in eine Datei umgelenkt wird, weswegen disown bequemer sein dürfte.
Anmerkung: Mit dem Kommando suspend kann man eine Bash selbst suspendieren. Sie wird dann erst weiter ausgeführt, wenn sie das Signal SIGCONT erhält. Zum Suspendieren einer Login-Shell muss bei suspend die Option -f (force) angegeben werden.
Die folgende nummerierte Liste zeigt die zusammengesetzten Kommandos (compound commands) der Bash, zu denen auch Kommandos zur Ablaufsteuerung gehören. Die Syntax-Angabe ist jeweils rot dargestellt, um sie optisch hervorzuheben:
(Liste)
Die Liste wird in einer Sub-Shell, also einem Kind-Prozess der aktuellen Shell ausgeführt. Variablenzuweisungen und eingebaute Kommandos der Liste, die die Ausführungs-Umgebung der Sub-Shell modifizieren (z.B. Verzeichnis-Wechsel oder Änderung der Signalbehandlung) haben daher keine Auswirkung auf die Umgebung der aktuellen Shell. Der Exit-Status dieses Konstrukts entspricht dem Exit-Status der ausgeführten Liste.
Diese Sub-Shell-Liste wird meist dort eingesetzt, wo man eine Kommando-Gruppe in einer geänderten Umgebung ausführen möchte, ohne die Umgebung der aktuellen Shell zu beeinflussen. Häufig geht es darum, einige Kommandos mit einem geänderten aktuellen Verzeichnis abzuarbeiten.
Beispiel:
(cd /tmp && touch daten) pwdDie Sub-Shell wechselt hier ins Verzeichnis /tmp und legt dort die Datei daten an. Der Verzeichnis-Wechsel hat aber keine Auswirkungen auf die aktive Shell. Deren aktuelles Verzeichnis bleibt unverändert. Davon kann man sich an Hand der Ausgabe von pwd überzeugen.
{ Liste; }
Die Liste wird in der aktuellen Shell ausgeführt. Sie muss mit einem Semikolon oder einem Newline-Zeichen abgeschlossen werden. Dieses Konstrukt dient als Gruppierungs-Kommando zur Bildung einer Kommando-Gruppe, das man z.B. benutzen kann, um für mehrere Kommandos gemeinsam eine Ein- oder Ausgabe-Umlenkung zu formulieren. Sein Exit-Status entspricht dem Exit-Status der ausgeführten Liste.
Bei den runden Klammern der oben erläuterten Sub-Shell-Liste handelt es sich um Metazeichen der Shell, die daher als Worttrenner wirken. Im Gegensatz dazu sind die geschweiften Klammern des Gruppierungs-Kommandos keine Worttrenner, sondern reservierte Wörter der Bash, die nur dort auftreten dürfen, wo reservierte Wörter zulässig sind. D.h., vor und hinter jeder der beiden Klammern muss ein Worttrenner stehen.
Konkret bedeutet dies, dass die öffnende Klammer durch mindestens einen Leerraum oder ein Newline-Zeichen von der Liste getrennt werden muss. Die schließende Klammer kann unmittelbar hinter dem abschließenden Semikolon der Liste folgen, allerdings sollte man der Übersichtlichkeit halber vor der schließenden Klammer auch mindestens einen Leerraum oder ein Newline-Zeichen angeben.
Beispiel:
{ cd /tmp ; touch daten ; } pwdDa hier im Gegensatz zum obigen Sub-Shell-Beispiel die Liste innerhalb der aktuellen Shell ausgeführt wird, wird deren aktuelles Verzeichnis nach /tmp gewechselt.
In diesem Beispiel macht die Verwendung der Gruppierung keinen praktischen Sinn. Man kann die geschweiften Klammern weglassen, ohne die Funktionalität zu ändern, da in beiden Fällen die drei angegebenen Kommandos sequentiell in der aktuellen Shell ausgeführt werden.
Das folgende Beispiel zeigt eine sinnvolle Anwendung des Gruppierungs-Kommandos, die es gestattet, eine gemeinsame Ein-/Ausgabe-Umlenkung für die Kommando-Gruppe zu formulieren und diese Kommando-Gruppe als ein Kommando einer Pipeline zu verwenden:
{ echo 'Daten von stdin:' cat echo ============= echo 'Inhalt der Datei "daten"' cat daten echo ============= } < /etc/hosts 2>/tmp/error | tee /tmp/out.txtDurch die Eingabe-Umlenkung liest das erste cat-Kommando die Datei /etc/hosts. Alle Ausgaben von echo und cat werden über die Pipe an das Kommando tee weitergegeben, das sie auf die Standard-Ausgabe (bei interaktiven Shells in der Regel das Terminal) sowie in die Datei /tmp/out.txt schreibt. Evtl. Fehlermeldungen der Kommando-Gruppe (z.B. bei Nicht-Existenz der Datei daten) werden in die Datei /tmp/error umgelenkt.
Das nächste Beispiel zeigt die Verwendung einer Kommando-Gruppe in einer UND- bzw. ODER-Liste:
[[ -d /tmp ]] && { echo /tmp existiert ; cd /tmp ; } [[ -d /tmp ]] || { echo /tmp existiert nicht ; exit 1 ; }Wenn bei der UND-Liste das Verzeichnis /tmp existiert, wird die Kommando-Gruppe ausgeführt. Sie weist durch eine Ausschrift auf die Existenz von /tmp hin und wechselt in dieses Verzeichnis.
Bei der ODER-Liste wird die Kommando-Gruppe abgearbeitet, wenn /tmp nicht existiert. Dann erfolgt eine Fehlermeldung sowie die Beendigung der Shell mit dem Exit-Status 1.
Die Kommando-Gruppierung kann man auch für eine Fehlerbehandlung in Skripten benutzen. Im folgenden Beispiel wird das Skript mit dem Exit-Status 1 beendet, wenn die Kommando-Gruppe einen Exit-Status ungleich 0 liefert:
{ cd /tmp && wc -l file1 > file2 && echo Ergebnis: $(< file2) } || exit 1Bei dem Gesamt-Konstrukt handelt es sich um eine ODER-Liste, deren zweites Element das einfache Kommando exit 1 ist. Statt eines einfachen Kommandos kann man natürlich auch eine weitere Kommando-Gruppe angeben:
{ cd /tmp && wc -l file1 > file2 && echo Ergebnis: $(< file2) } || { echo "Kommando schlug fehl; Exit-Status: $?" exit 1 }Da in den letzten beiden Beispielen die Kommandos der Kommando-Gruppe eine UND-Liste bilden, liefert die Gruppe nur dann den Exit-Status 0 (also Erfolg), wenn jedes Kommando der Gruppe erfolgreich ausgeführt wurde. Sobald eines der Kommandos fehlschlägt, wird dessen Exit-Status als Exit-Status der Gruppe zurückgeliefert.
Anmerkung: In den genannten beiden Beispielen kann man auf die Gruppierung der UND-Liste durch geschweifte Klammern verzichten, weil das rechte Kommando der ODER-Liste generell nur dann ausgeführt wird, wenn die UND-Liste nicht erfolgreich war, da die Operatoren && und || denselben Vorrang haben. Die Gruppierung ist allerdings dennoch sinnvoll, da sie im allgemeinen die Lesbarkeit des Konstrukts erhöht und außerdem die Möglichkeit bietet, den Exit-Status der gesamten Kommando-Gruppe zu negieren, was wir im nächsten Beispiel zeigen, sowie für die Gruppe geschlossen eine Ein-/Ausgabe-Umlenkung zu formulieren.
Da eine Kommando-Gruppe wiederum ein Kommando und somit eine einelementige Pipeline ist, lässt sich ihr Exit-Status auch negieren, z.B. so:
if ! { cd /tmp && wc -l file1 > file2 && echo Ergebnis: $(< file2) } then echo 'Kommando-Gruppe schlug fehl' exit 1 fiDurch die Negation enthält der spezielle Parameter $? hier den negierten Exit-Status der Kommando-Gruppe, im Fehlerfalle also immer eine 0 und im Erfolgsfalle immer eine 1.
Möchte man im Rahmen der Fehlerbehandlung auf den wirklichen Exit-Status der Kommando-Gruppe zugreifen können, muss man auf die Negation verzichten. Die folgenden beiden Beispiele zeigen eine mögliche Implementierung:
if { cd /tmp && wc -l file1 > file2 && echo Ergebnis: $(< file2) } then echo 'Kommando-Gruppe war erfolgreich' else echo "Kommando-Gruppe schlug fehl; Exit-Status: $?" exit 1 fi # andere Implementierung: { cd /tmp && wc -l file1 > file2 && echo Ergebnis: $(< file2) } ret=$? if ((ret)) then echo "Kommando-Gruppe schlug fehl; Exit-Status: $ret" else echo 'Kommando-Gruppe war erfolgreich' fi
Wenn man im Erfolgsfalle nichts tun möchte, kann man statt echo das leere Kommando verwenden, dessen Name ein Doppelpunkt ist:
if (($#)) then # wir tun nichts, da die Anzahl der Argumente ($#) ungleich Null ist : else echo 'Fehler, da keine Argumente angegeben wurden' fiEin Kommentar allein statt des leeren Kommandos genügt nicht, da dann das Kommando if eine falsche Syntax hat, weil mindestens ein Kommando im then- und else-Zweig stehen muss.
Anmerkung:
Wenn man eine Kommando-Gruppe in den Hintergrund verlagert, wird analog zur Ausführung einfacher Hintergrund-Kommandos zuerst die Sub-Shell kreiert und dann dort die Kommando-Gruppe ausgeführt. Daher wirken die innerhalb der Kommando-Gruppe realisierten Zuweisungen, Verzeichnis-Wechsel etc. nur in der im Hintergrund gestarteten Sub-Shell, nicht aber in der aktuellen Shell, wie folgendes Beispiel demonstriert:
a=5 cd / { a=6 ; cd /tmp ; } & echo "$a" pwdNach Ausführung der Kommando-Gruppe hat die Variable a weiterhin den Wert 5. Die Shell steht auch weiterhin im Verzeichnis /.
Wenn man ohnehin vor hat, eine Kommando-Gruppe im Hintergrund auszuführen, kann man also zur Gruppierung auch die geschweiften statt der runden Klammern (mit denen eine Sub-Shell-Liste erzeugt wird) nutzen. In beiden Fällen wird eine Rückwirkung auf die Umgebung der aktuellen Shell verhindert, weil die Kommando-Gruppe in der Sub-Shell zur Ausführung kommt.
Will man eine solche Rückwirkung verhindern, die Kommando-Gruppe aber synchron ausführen, dann muss man die Sub-Shell-Liste verwenden, weil dann die Kommando-Gruppe wieder in der Sub-Shell ausgeführt wird, die aktive Shell aber auf deren Ende wartet.
[[ Ausdruck ]]
Dieses Konstrukt dient der Auswertung eines Test-Ausdrucks, mit dem man Datei-Attribute überprüfen sowie arithmetische und String-Vergleiche durchführen kann. Bei erfolgreichen Tests wird der Exit-Status 0 geliefert, sonst 1.
Die im Test-Ausdruck nutzbaren Operatoren sind im Abschnitt Test-Operatoren und Bedingungs-Ausdrücke detailliert beschrieben.
Tests der Form [[ ... ]] werden von der Bash intern ausgewertet. Dieses Konstrukt stammt von der Korn-Shell und ist auch in der Z Shell zu finden. Es wird dabei also kein externes Kommando gestartet.
Die alte Bourne-Shell kannte diesen Test noch nicht. Dort musste man auf das externe Kommando test zurückgreifen, das den Alternativ-Namen [ trägt. Der Test-Ausdruck ist hier mit dem Wort ] abzuschließen: [ ... ].
Die alte Form des Tests steht auch bei den neueren Shells wie Korn-Shell, Bash und Z Shell zur Verfügung, wobei hier das Kommando test bzw. [ ein eingebautes Kommando ist. Zusätzlich existiert weiterhin das externe Kommando test, das aber ggf. weniger Operatoren als das entsprechende Builtin-Kommando unterstützt.
Die beiden Test-Formen sind in ihrer Anwendung ähnlich, da sie dieselbe Menge von Bedingungs-Ausdrücken unterstützen. Details sind aber verschieden. Bei der neuen Form [[ ... ]] erfolgt im Unterschied zur alten Form innerhalb des Test-Ausdrucks
keine Aufspaltung in Wörter (Word Splitting) und
keine Pfadnamens-Expandierung.
Meist wird genau dieses Verhalten gewünscht. In der alten Test-Form muss die Aufspaltung in Wörter bei Bedarf durch geeignete Quotierung erreicht werden. Hier ein Beispiel:
var="1 2 3" # Bei test und [ ist eine Quotierung nötig: test -n "$var" && echo var ist nicht leer [ -n "$var" ] && echo var ist nicht leer # Bei [[ kann die Quotierung entfallen: [[ -n $var ]] && echo var ist nicht leerTest-Operatoren wie -f (zum Test auf eine reguläre Datei) müssen bei [[ ... ]] unquotiert notiert werden, um in der gewünschten Weise zu funktionieren. Bei test ist dagegen eine Quotierung erlaubt.
Beispiele für zulässige Konstrukte:[ -f xyz ] && echo xyz existiert [ '-f' xyz ] && echo xyz existiert [[ -f xyz ]] && echo xyz existiertUnzulässig ist dagegen z.B.:
[[ '-f' xyz ]] && echo xyz existiertBei den Operatoren == und != wird der rechte Operand als Muster betrachtet und gemäß den Regeln für Muster bei der Pfadnamens-Expandierung interpretiert. Beliebige Teile des Musters können quotiert werden, wodurch für sie ein exakter String-Vergleich bewirkt wird.
Ab Bash 3.0 unterstützt [[ ... ]] auch noch den Operator =~, der denselben Vorrang wie == und != hat und dessen rechter Operand als erweiterter regulärer Ausdruck gemäß POSIX 1003.2 interpretiert wird (s. dazu regexp(7) im Online-Manual).
Test-Ausdrücke können bei [[ ... ]] durch folgende Operatoren kombiniert werden, die hier fallend nach ihrem Vorrang angegeben sind:
( Ausdruck )
- gibt den Wert von Ausdruck zurück
- kann zur Veränderung des normalen Operator-Vorrangs genutzt werden
! Ausdruck
- logische Negation von Ausdruck
Ausdruck && Ausdruck
- logisches UND
- wahr, wenn beide Ausdrücke wahr sind
Ausdruck || Ausdruck
- logisches ODER
- wahr, wenn mindestens einer der beiden Ausdrücke wahr ist
Die runden Klammern dürfen bei [[ nicht quotiert werden. Bei test dagegen müssen sie quotiert werden, da sie von der Shell als Zeichen mit Sonderbedeutung (Metazeichen) angesehen werden.
Bei && und || wird der zweite Ausdruck nicht ausgewertet, wenn bereits nach Auswertung des ersten Ausdrucks das Resultat des Gesamtausdrucks feststeht (sog. Short-Circuit-Evaluation).
Die folgenden Beispiele zeigen verschiedene Anwendungen von Tests:
name=Otto [[ $name == O* ]] && echo "$name beginnt mit O" [[ $name != O* ]] && echo "$name beginnt nicht mit O" [[ -d /tmp ]] && echo '/tmp ist ein Verzeichnis (Directory)' [[ ! -d /tmp ]] && echo '/tmp ist kein Verzeichnis (Directory)' [[ -r /etc/passwd ]] && echo '/etc/passwd ist lesbar' [[ -n $BASH ]] && echo Variable BASH ist nicht leer [[ -z $BASH ]] && echo Variable BASH ist leer oder existiert nicht [[ $name = Otto ]] || echo 'Inhalt von Variable "name" ist nicht Otto' [[ " 17" -eq 17 ]] && echo beide Operanden haben den numerischen Wert 17 [[ $a < $b ]] && echo 'Wert von Variable "a" ist lexikografisch kleiner als Wert von Variable "b"' [[ $a -lt $b ]] && echo Wert von Variable a ist numerisch kleiner als Wert von Variable b [[ $name == O* || ((${#name} == 10)) ]] && echo "$name beginnt mit O oder ist 10 Zeichen lang" [[ $name == O* && ((${#name} == 10)) ]] && echo "$name beginnt mit O und ist 10 Zeichen lang" [[ ( ${name:0:1} == Z || ${name:0:1} == O ) && ( ${#name} -gt 3 ) ]] && echo "$name beginnt mit Z oder O und hat eine Länge größer 3" # Auch die erweiterten Pfadnamens-Muster sind bei == und != nutzbar. # Im folgenden Kommando wird getestet, ob das aktuelle Skript vom Nutzer # max oder moritz gerufen oder über einen Namen gestartet wurde, der # den Bestandteil tausch trägt: if [[ $USER == @(max|moritz) || $0 == *tausch* ]] then echo 'Bedingung erfüllt' # hier können weitere Kommandos folgen fi # Anmerkung: Über symbolische Links kann man einem Skript sehr bequem # verschiedene Namen zuordnen. In Abhängigkeit vom Namen, der über # den Parameter $0 ermittelbar ist, kann das Skript dann verschiedene # Aktionen ausführen. # Unterstützung regulärer Ausdrücke bei Bash 3.0: [[ $name =~ '^O..o$' ]] && echo 'Wert der Variablen "name" entspricht dem Muster "O..o"'
for Name [ in Wort-Liste ] ; do Liste ; done
Diese Konstruktion dient dem sequentiellen Durchlaufen einer Liste. Wenn der optionale Teil in Wort-Liste fehlt, wird die Liste der gesetzten Positionsparameter (Argumente eines Shell-Skripts oder einer Shell-Funktion) durchlaufen.
Der Exit-Status dieser for-Schleife entspricht dem Exit-Status der ausgeführten do-Liste. Wenn die Wort-Liste leer ist, wird kein Kommando des Schleifenkörpers ausgeführt. Der Exit-Status ist dann 0.
Beispiel:
for i in $(seq 1 10) * do echo "$i" doneDie Variable i durchläuft zunächst die Liste der ganzen Zahlen von 1 bis 10, die durch das externe Kommando seq erzeugt wird, und danach die durch die Pfadnamens-Expandierung des Musters * gebildete Liste aller Dateinamen des aktuellen Verzeichnisses.
Ab Bash 3.0 kann man statt seq auch die Brace-Expandierung mit Sequenz-Ausdruck benutzen:
for i in {1..10} * do echo "$i" done
for (( Ausdruck1 ; Ausdruck2 ; Ausdruck3 )) ; do Liste ; done
Dieses Konstrukt erinnert an die for-Schleife, wie man sie z.B. bei den Programmiersprachen C oder AWK findet. Allerdings gilt gegenüber anderen Sprachen bei der Bash die Einschränkung, dass die drei Ausdrücke immer nur numerisch ausgewertet werden, so dass keine String-Operationen nutzbar sind.
Zuerst wird der Ausdruck1 ausgewertet. Danach wird der Ausdruck2 wiederholt so lange ausgewertet, bis er den Wert 0 hat. In diesem Falle endet die Schleife. Solange Ausdruck2 einen Wert ungleich 0 hat, wird der Schleifenkörper (also die do-Liste) ausgeführt und danach Ausdruck3 ausgewertet. Alle drei Ausdrücke sind optional. Jeder fehlende Ausdruck wird zu 1 ausgewertet.
Der Exit-Status dieser for-Schleife entspricht dem Exit-Status der do-Liste oder 1, wenn einer der Ausdrücke ungültig ist.
Beispiel:
for (( i=1 ; i < 10 ; i++ )) do echo "$i" doneHier wird eine Zählschleife realisiert, deren Körper 9 Mal durchlaufen wird. Die Variable i bekommt der Reihe nach die ganzen Zahlen von 1 bis 10 zugewiesen. Solange ihr Wert kleiner als 10 ist, wird der Schleifenkörper ausgeführt und somit der Wert von i ausgegeben.
select Name [ in Wort-Liste ] ; do Liste ; done
Dieses Konstrukt ermöglicht die recht bequeme Gestaltung einfacher Text-Menüs. Die Elemente der Wort-Liste werden mit 1 beginnend aufsteigend nummeriert als Menü auf die Standard-Ausgabe geschrieben. Falls der optionale Teil in Wort-Liste fehlt, werden die gesetzten Positionsparameter verwendet.
Nach der Ausgabe dieses Menüs wird Prompt PS3 ausgegeben, eine Zeile von der Standard-Eingabe eingelesen und in der Variablen REPLY gespeichert. Die Umgebungsvariable TMOUT ist zur Einstellung eines Timeouts nutzbar.
Wurde eine Nummer eingelesen, die mit einem Element der Wort-Liste korrespondiert (die also als Nummer eines Menü-Eintrags vorkommt), so erhält die Variable Name das zugehörige Wort der Wort-Liste (also den Menü-Eintrag) als Wert. Wurde der leere String eingelesen, werden die nummerierte Wort-Liste sowie PS3 erneut ausgegeben. Falls EOF (End Of File) gelesen wurde, wird select beendet. Bei jedem anderen eingelesenen Wert wird Name mit dem leeren String initialisiert.
Nach jeder nichtleeren Auswahl wird der Schleifenkörper (do-Liste) bis zum ersten Kommando break oder (falls kein break erreicht wird) bis zum Ende ausgeführt. Durch break wird select beendet. Ohne break schließt sich an die Abarbeitung der do-Liste eine erneute Auswahl an, wobei aber das Auswahl-Menü nicht erneut ausgegeben wird. Falls man vor jeder Auswahl das Menü wieder anzeigen lassen möchte, muss man select in einer geeigneten Schleife wiederholen.
Der Exit-Status von select entspricht dem Exit-Status der do-Liste oder ist 0, falls keine do-Liste ausgeführt wurde.
Das folgende Beispiel, das auch unter SCRIPTS/select.sh zu finden ist, soll die Nutzung von select verdeutlichen. Die Kommentare enthalten Erläuterungen zu den einzelnen Kommandos und Sachverhalten:
#!/bin/sh # # Beispiel zu select # # 14.1.2006 # Durch die while-Schleife wird das Auswahl-Menü vor jeder Auswahl ausgegeben. while ((1)) do # Wir löschen vor select immer die Variable name. An einem leeren String # in name erkennen wir EOF, da select in diesem Falle die Variable # unverändert lässt. unset name select name in Anton Berta Caesar do # Wenn name nicht leer ist, liegt eine korrekte Auswahl vor. Dann brechen # wir select ab, um vor der nächsten Auswahl wieder das Menü angezeigt # zu bekommen. Bei einer ungültigen Auswahl ist name leer. Dadurch wird # der Körper von select bis zum Ende abgearbeitet, was zur Folge hat, # dass wir select nicht beenden, so dass sich eine neue Auswahl ohne # Ausgabe des Menüs anschließt. [[ -n $name ]] && break done # Bei EOF brechen wir das Skript ab. Mit break statt exit könnte man auch # nur die while-Schleife abbrechen. Falls select innerhalb einer Funktion # ausgeführt würde, könnte man mit return statt exit diese Funktion # abbrechen. [[ -z $name ]] && { echo 'Abbruch ohne Auswahl' ; exit ; } echo "Auswahl: $name" done
case Wort in [ [(] Muster [ | Muster ] ... ) Liste ;; ] ... esac
Mit case kann man eine Mehrfach-Verzweigung bzw. eine Fall-Auswahl realisieren. Das Wort wird expandiert und anschließend der Reihe nach mit jedem angegebenen Muster verglichen, wobei die Regeln der Pfadnamens-Expandierung zur Anwendung kommen. Sobald ein Muster passt, wird die zugehörige Liste ausgeführt und die case-Anweisung beendet. D.h., weitere Muster werden dann nicht getestet.
Der Exit-Status von case ist 0, wenn kein Muster passt. Ansonsten entspricht er dem Exit-Status der ausgeführten Liste.
Beispiel:
read -p 'Antwort (j/n): ' antwort case $antwort in j|J) echo Ja ;; n|N) echo Nein ;; *) echo ungültige Auswahl ;; esac
if Liste; then Liste; [ elif Liste; then Liste; ] ... [ else Liste; ] fi
Mit dem Kommando if lassen sich vollständige und unvollständige Alternativen realisieren. Zuerst wird die if-Liste (also die Liste hinter if) ausgeführt. Wenn deren Exit-Status 0 lautet, wird die (erste) then-Liste ausgeführt und die Alternative beendet. Andernfalls wird begonnen, der Reihe nach alle existierenden elif-Listen auszuführen. Sobald eine den Exit-Status 0 hat, wird die zugehörige then-Liste ausgeführt und die Alternative beendet. Andernfalls wird eine existierende else-Liste ausgeführt.
Der Exit-Status der Alternative ist 0, wenn keine then- oder else-Liste ausgeführt wurde, wenn also keine Bedingung wahr war, d.h. den Exit-Status 0 lieferte. Andernfalls entspricht der Exit-Status der Alternative dem Exit-Status der ausgeführten then- bzw. else-Liste.
Beispiel:
read -p 'Zahl: ' zahl if [[ $zahl -gt 15 ]] then echo "$zahl ist größer als 15" elif [[ $zahl -gt 5 ]] then echo "$zahl ist größer als 5" else echo "$zahl ist kleiner gleich 5" fi
while Liste; do Liste; done
until Liste; do Liste; done
Diese beiden Konstrukte dienen der Realisierung von Abweis-Schleifen. Die while-Schleife führt die do-Liste so lange aus, solange die Liste (Bedingung) hinter while den Exit-Status 0 liefert. Die until-Schleife entspricht der while-Schleife mit negiertem Test. Die do-Liste wird also so lange ausgeführt, solange die Liste (Bedingung) hinter until einen Exit-Status ungleich 0 liefert.
Der Exit-Status der Abweis-Schleifen entspricht dem Exit-Status der ausgeführten do-Liste oder 0, falls keine do-Liste ausgeführt wurde.
Beispiel:
zahl=5 while ((zahl > 0)) do echo "$zahl" ((zahl--)) done zahl=5 until ((zahl == 0)) do echo "$zahl" ((zahl--)) doneDiese beiden Schleifen sind funktionell identisch. Sie dekrementieren schrittweise den Inhalt der Variablen zahl von 5 auf 0 und geben diesen Inhalt aus, solange er größer als 0 ist.
Die Bash ermöglicht auch eine Schleifensteuerung. Die mit for, while, until und select realisierbaren Schleifen können durch die Kommandos break und continue beeinflusst werden:
break: Abbruch der Schleife
continue: sofortiger Beginn der nächsten Iteration der Schleife
Optional kann bei break und continue ein numerisches Argument n angegeben werden, um sich auf die n-te umschließende Schleife zu beziehen. n hat den Standard-Wert 1.
Beispiele:
# Endlos-Einlese-Schleife while true do # Zeile von stdin einlesen read # Doppelkreuz-Kommentare übergehen [[ $REPLY == \#* ]] && continue # bei leerer Zeile Einlese-Schleife stoppen [[ -z $REPLY ]] && break # eingelesene Zeile verarbeiten (ausgeben) echo "$REPLY" done # break mit Argument: # Sobald hier die beiden Variablen i und j denselben # numerischen Wert haben, werden beide Schleifen beendet. for i in 3 4 5 do for j in 4 5 6 do echo "$i $j" ((i == j)) && break 2 done done echo Ende der beiden for-Schleifen
Im Gegensatz zu ksh, zsh und gawk unterstützt die Bash vor Version 4 keine Koprozesse:
cat |& # Koprozess in der ksh einrichten coproc cat # Koprozess in der zsh einrichten # bidirektionale Kommunikation mit dem Koprozess in ksh/zsh # # Zur Kommunikation mit dem Koprozess existiert bei den Kommandos # print und read die Option -p. Außerdem kann man mit # dem Umlenkungs-Operator <& p Daten vom Koprozess lesen und # mit dem Operator >& p Daten zum Koprozess senden. print -p abc echo def >& p read -p ; echo "$REPLY" read <& p ; echo "$REPLY" # Koprozess-Beispiel bei gawk: Sortierung von Zeichenketten # unter Verwendung des Unix-Standard-Kommandos sort BEGIN { SORT = "sort" # 3 Zeichenketten an sort senden print "def" |& SORT print "xyz" |& SORT print "abc" |& SORT # die Verbindung in Richtung von gawk zu sort schließen; # dadurch erhält sort ein EOF (Dateiende-Signal, End Of File) # und beginnt die Sortierung close(SORT, "to") # die sortierten Daten in einer Schleife zurücklesen und ausgeben while ((SORT |& getline str) > 0) { print str } }
Ab Version 4 stehen auch in der Bash Koprozesse zur Verfügung. Das sind Shell-Kommandos, die wie bei der Z-Shell mit dem reservierten Wort coproc eingeleitet werden. Die komplette Syntax sieht so aus:
coproc [NAME] Kommando [Umlenkungen]
Dadurch wird der Koprozess NAME erzeugt. Falls NAME nicht angegeben wird, verwendet die Bash den Default-Namen COPROC. NAME darf nur dann spezifiziert werden, wenn es sich beim Koprozess-Kommando um ein zusammengesetztes Kommando handelt. Bei einem einfachen Kommando würde NAME sonst als dessen erstes Wort interpretiert werden.
Ein solcher Koprozess wird asynchron in einer Sub-Shell ausgeführt, als ob das Kommando mit dem Operator & abgeschlossen worden wäre, allerdings wird hier zusätzlich eine bidirektionale Pipe zwischen der aufrufenden Shell und dem Koprozess etabliert. Im Kontext der aufrufenden Shell wird eine Feld-Variable NAME erzeugt. Die Standardausgabe des Koprozess-Kommandos ist mit einer Pipe verbunden, deren Dateideskriptor in NAME[0] hinterlegt wird. Analog steht in NAME[1] der Dateideskriptor der Pipe, die mit der Standardeingabe des Koprozess-Kommandos verbunden ist. Die Pipe wird eingerichtet, bevor die optionalen Umlenkungen des Kommandos coproc aktiv werden, so dass die Dateideskriptoren der Pipe dort nutzbar sind.
Die Prozess-ID der den Koprozess ausführenden Sub-Shell ist als Wert der Variable NAME_PID verfügbar. Das eingebaute Kommando wait kann genutzt werden, um auf die Beendigung des Koprozesses zu warten. Dessen Exit-Status entspricht dem Exit-Status des Koprozess-Kommandos.
Hinweis: Eine Bash-Instanz gestattet zu jeder Zeit maximal einen aktiven Koprozess. Details hierzu findet man unter Only one coprocess at a time.
Beispiel für Bash-Koprozesse: SCRIPTS/coprocess.sh
#!/bin/sh # # Koprozesse ab Bash 4 # # 4.5.2014 # einen Koprozess mit einem einfachen Kommando erzeugen; awk konvertiert alle # von der Standardeingabe gelesenen Zeilen in Großschreibung und gibt das # Resultat auf die Standardausgabe aus; durch fflush() wird trotz aktiver # Bufferung eine Ausgabe ermöglicht; siehe den Punkt "Buffering" unter # http://wiki.bash-hackers.org/syntax/keywords/coproc?s[]=coproc#pitfalls coproc awk '{print toupper($0) ; fflush() }' # wir übergeben in einer Schleife 3 Strings an die Standardeingabe des # Koprozesses und lesen dessen Ausgabe zurück und geben sie aus; die # Deskriptoren der bidirektionalen Pipe zum Koprozess stehen im Feld COPROC for text in aaa bbb ccc do # eine Zeile an den Koprozess senden echo "$text" >&${COPROC[1]} # eine Zeile vom Koprozess lesen read line <&${COPROC[0]} echo "$line" done # den Koprozess mit kill und wait sauber beenden; # ohne Umlenkung auf /dev/null bekämen wir von wait eine Nachricht der Art # coprocess.sh: Zeile 30: 27410 Beendet coproc COPROC awk '{print toupper($0) ; fflush() }' kill $COPROC_PID wait $COPROC_PID &> /dev/null echo ==== # einen zweiten Koprozess erzeugen; diesmal geben wir ihm den expliziten Namen # "copr"; daher muss das Koprozess-Kommando ein zusammengesetztes Kommando # sein; die Standardausgabe des Koprozess-Kommandos ist zunächst mit der # bidirektionalen Pipe verbunden; durch die Umlenkung >&3; wird sie auf den # Dateideskriptor 3 umgelenkt, den wir durch eine äußere Umlenkung mit der # Standardausgabe des aufrufenden Skripts verbinden, so dass dort automatisch # die Ausgaben des Koprozesses erscheinen { coproc copr { awk '{print tolower($0) ; fflush() }'; } >&3; } 3>&1 for text in aaa bbb ccc do echo "$text" >&${copr[1]} done # das explizite Vernichten des Koprozesses durch # kill $copr_PID # wait $copr_PID # kann hier entfallen, da dies die Bash am Ende selber erledigt echo Ende
Die Bash gestattet die Definition eigener Funktionen. Bei einer Shell-Funktion handelt es sich um mit Shell-Mitteln definiertes Konstrukt, das wie ein einfaches Kommando gerufen werden kann und ein zusammengesetztes Kommando (den Funktionskörper) mit einem neuen Satz von Positionsparametern (nummerierte Argumente, die über Konstrukte der Art $1, $2, ${11} angesprochen werden) ausführt.
Syntax einer Funktions-Definition:
[function] Name () { Liste; }
Anmerkung: Wenn das reservierte Wort function angegeben wurde, können die runden Klammern hinter dem Funktionsnamen entfallen.
Der Exit-Status einer Funktion entspricht im Standardfall dem Exit-Status des letzten Kommandos, das in der Funktion ausgeführt wurde. Mit dem eingebauten Kommando
return [n]
kann man eine Funktion beenden und gleichzeitig den Exit-Status gezielt auf den Wert n setzen. Wenn n fehlt, entspricht der Exit-Status der Funktion dem Exit-Status des zuletzt im Funktionskörper ausgeführten Kommandos bzw. 0, wenn return das erste Kommando des Funktionskörpers ist.
Beispiele:
# Programm-Beendigung mit Fehlermeldung nach Fehler function error() { # optionales Argument ist Fehlernummer local nr=$1 # Standard-Fehlernummer ist 0 [[ -n $nr ]] || nr=0 echo "Das Programm wird wegen Fehler $nr beendet." exit 1 } # Prozess-Liste nach einem AWK-Muster durchsuchen # interne Funktion zum Durchsuchen der Prozess-Status-Liste, wobei das # Such-Kommando ausgeblendet werden soll function psg_intern() { ps auxww | sed 1d | $1 "$2" | fgrep -v "$1 $2" } # ps grep -- Ausgabe von ps nach AWK-Muster durchsuchen function psg() { psg_intern awk "/$1/" } # analog psg(), aber ohne Unterscheidung Groß-/Kleinschreibung function psgi() { psg_intern 'awk -v IGNORECASE=1' "/$1/" } # Funktion mit return-Kommando # Ein Exit-Status 0 zeigt den Erfolg der Funktion an. # Ein Exit-Status größer 0 benennt das Kommando, das fehlschlug. function putzen() { cd /tmp/xxx || return 1 rm *.bak || return 2 } # Funktion putzen aufrufen und Exit-Status auswerten putzen exit_status=$? ((exit_status)) && echo "Fehler bei Kommando $exit_status"
Wenn man eine Funktion innerhalb einer Shell ausführt, dann kann sie (im Gegensatz zu externen Kommandos, die immer in einem Kind-Prozess laufen) die Ausführungsumgebung dieser Shell verändern. Um eine solche Veränderung der Umgebung zu verhindern, muss man die Funktion in einer Sub-Shell ablaufen lassen. Das soll an folgendem Beispiel demonstriert werden:
function xyz() { zzz=888 cd /etc/init.d } unset zzz cd / ( xyz ) # Funktion xyz synchron in einer Sub-Shell ausführen pwd echo zzz=$zzz # Ausgabe: # / # zzz= # Wie erwartet wurde in der aktuellen Shell das aktuelle Verzeichnis nicht geändert # und die globale Variable zzz nicht angelegt. xyz & # Funktion xyz asynchron in einer Sub-Shell ausführen wait # auf das Ende des Hintergrund-Kommandos warten pwd echo zzz=$zzz # Ausgabe: # / # zzz= # Auch hier wurde erwartungsgemäß in der aktuellen Shell das aktuelle Verzeichnis # nicht geändert und die globale Variable zzz nicht angelegt. xyz # Funktion xyz synchron in der aktuellen Shell ausführen pwd echo zzz=$zzz # Ausgabe: # /etc/init.d # zzz=888 # Im Gegensatz zu den beiden vorangegangenen Aufrufen hat die Funktion # xyz die Umgebung der aktuellen Shell verändert. Deren neues # aktuelles Verzeichnis lautet /etc/init.d. Außerdem wurde die # globale Variable zzz mit dem Wert 888 angelegt.
Lokale Variablen können mit den Kommandos local oder declare (bzw. dem mittlerweile als veralt geltenden typeset) angelegt werden:
name=Max function print_name() { echo "$name" local name=Otto declare name1=Berta echo "$name $name1" } print_name echo "$name $name1"
Lokale Variablen sind nur in der Funktion, in der sie definiert werden, und in allen Funktionen, die von der definierenden Funktion aufgerufen werden, sichtbar.
Alle nicht lokalen Variablen sind global, also im gesamten Skript sichtbar. Wenn eine Funktion einer noch nicht existierenden Variablen, die nicht als lokale Variable deklariert wurde, einen Wert zuweist, dann wird diese Variable als neue globale Variable angelegt. Das hatten wir oben in der Funktion xyz gesehen, die die globale Variable zzz anlegte.
Signale gehören bei Unix/Linux-Systemen zu den Mitteln der Interprozess-Kommunikation. Sie haben eine Nummer sowie einen symbolischen Namen, die beide zur eindeutigen Identifizierung des Signals genutzt werden können. So trägt z.B. das Signal 9 die Bezeichnung SIGKILL. Zum Umgang mit Signalen existieren in der Bash die eingebauten Kommandos kill und trap.
Mit kill, das auch bei der Job-Steuerung eine Rolle spielt, kann man Signale an andere Prozesse senden. Neben dem eingebauten Kommando kill gibt es auch noch ein externes Kommando kill. Allerdings bieten alle modernen Shells ein eigenes kill an, das sich zwischen den verschiedenen Shells mehr oder minder deutlich unterscheidet. Nachfolgend wollen wir uns daher nur auf das Bash-interne kill beziehen.
Mit trap kann man die Reaktion auf die vom Shell-Prozess empfangenen Signale festlegen, indem man jeweils bestimmten Signalen einen entsprechenden Signal-Handler zuordnet.
Wie man z.B. der Seite signal(2) des Online-Manuals, die den Systemruf signal() beschreibt, entnehmen kann, kennt Unix/Linux drei Arten von Signal-Handlern:
eine nutzerdefinierte Funktion (nutzerdefinierter Code)
die Konstante SIG_IGN zum Ignorieren eines Signals
die Konstante SIG_DFL zur Reinstallation des Vorzugs-Verhaltens des Unix-/Linux-Kerns, das je nach Signal verschieden sein kann:
Bei einem Signal-Handler der Bash handelt es sich um eine Folge von Shell-Kommandos, die ausgeführt wird, wenn die Shell eines der Signale empfängt, denen der Handler zugeordnet wurde bzw. die dem Handler zugeordnet wurden. Eine derartige von der Shell verwaltete Verknüpfung eines Handlers mit einem Signal wird auch als Trap bezeichnet.
Wenn ein solcher Signal-Handler existiert und mindestens ein Kommando umfasst, dann werden die zugehörigen Signale abgefangen und im (vom Shell-Anwender frei programmierbaren) Handler gezielt behandelt. Dies entspricht dem obigen Handler Nummer 1 (benutzerdefinierte Funktion).
Anmerkung: Die Signale SIGKILL und SIGSTOP können generell nicht abgefangen werden, weil dies der Unix-/Linux-Kern nicht zulässt.
Ein Signal-Handler der Bash kann auch leer sein, also kein Kommando enthalten. In diesem Falle werden die zugehörigen Signale ignoriert (entspricht SIG_IGN). Die Zuordnung eines leeren Handlers wird aber von der Shell trotzdem in der Liste der aktiven Traps verwaltet.
Die Bash bietet die Variante SIG_DFL nicht direkt an. D.h., man kann nicht gezielt das Vorzugs-Verhalten des Kerns bzgl. der Signal-Behandlung reinstallieren. Statt dessen ermöglicht die Shell die Reinstallation derjenigen Einstellungen der Signal-Behandlung, die sie von ihrem Eltern-Prozess geerbt hat. Sobald für ein Signal dieser geerbte Handler reinstalliert wurde, taucht dieses Signal nicht mehr in der Liste der aktiven Traps auf.
Anmerkung: Vom Eltern-Prozess kann die Shell nur die Einstellungen SIG_DFL und SIG_IGN erben. Eine andere Möglichkeit lässt der Kern nicht zu. Sobald nämlich ein Prozess eine andere Anwendung startet, werden alle Signale, die mit einem benutzerdefinierten Signal-Handler verbunden sind, auf SIG_DFL gesetzt. Die mit SIG_IGN verbundenen Signale bleiben dagegen unangetastet, d.h., sie werden auch im neu gestarteten Programm ignoriert.
Die folgende Liste beschreibt, wie die Bash mit empfangenen Signalen umgeht:
Für eine interaktive Bash, in der keine Traps aktiv sind, gilt:
Das Signal SIGTERM wird ignoriert. Daher beendet das Kommando kill 0, mit dem das Signal SIGTERM an die (durch die Zahl Null symbolisierte) aktuelle Prozess-Gruppe gesendet wird, eine interaktive Shell nicht.
Das Signal SIGINT wird abgefangen und behandelt, weswegen das eingebaute Kommando wait unterbrechbar ist.
Das Signal SIGQUIT wird generell ignoriert.
Wenn die Job-Steuerung aktiv ist, werden die Signale SIGTTIN, SIGTTOU und SIGTSTP von der Shell ignoriert.
Die von der Bash gestarteten externen (also nicht eingebauten) Kommandos bekommen jene Signal-Handler reinstalliert, die der Shell von ihrem Eltern-Prozess vererbt wurden. Wenn die Job-Steuerung nicht aktiv ist, sorgt die Bash dafür, dass asynchrone Kommandos (also Hintergrund-Kommandos) unabhängig von den geerbten Signal-Handlern die Signale SIGINT und SIGQUIT ignorieren. Die im Rahmen der Kommando-Substitution ausgeführten Kommandos ignorieren die durch den Tastatur-Treiber generierten Signale der Job-Steuerung, also SIGTTIN, SIGTTOU und SIGTSTP.
Beim Empfang des Signals SIGHUP wird die Bash standardmäßig beendet. Zuvor sendet allerdings eine interakive Shell das Signal SIGHUP an alle Jobs (sowohl an laufende als auch gestoppte). Den gestoppten Jobs wird vorher ein SIGCONT geschickt, damit sie das SIGHUP auch empfangen können. Wenn man vermeiden möchte, dass ein bestimmter Job ein Signal von der Shell bekommt, sollte man ihn mit dem Kommando disown aus der Job-Liste streichen. Mit disown -h kann man einen Job so markieren, dass er speziell das Signal SIGHUP von der Shell nicht bekommt.
Falls die mit shopt-Option huponexit aktiv ist, sendet eine interaktive Bash das Signal SIGHUP an alle Jobs, wenn sie selbst beendet wird.
Wenn die Bash auf die Beendigung eines synchronen Kommandos wartet und in dieser Zeit ein Signal empfängt, für das ein Trap installiert wurde, dann wird der Trap erst nach der Beendigung dieses Kommandos ausgeführt. Wenn die Bash mittels wait auf ein asynchrones Kommando wartet und in dieser Zeit ein Signal empfängt, für das ein Trap installiert wurde, dann wird der Trap unmittelbar ausgeführt. Anschließend kehrt wait sofort mit dem Exit-Status 128+n zurück, wobei n die Nummer des empfangenen Signals ist.
Die drei Skripte
demonstrieren dieses Verhalten praktisch.
Das Kommando kill hat zwei Syntax-Varianten:
# Variante 1: kill [-s Signal-Spezifikation | -n Signal-Nummer | -Signal-Spezifikation] [PID | Job-Spezifikation] ... # Variante 2: kill -l [Signal-Spezifikation | Exit-Status]
Variante 1 sendet das durch eine Signal-Spezifikation oder eine Signal-Nummer angegebene Signal an diejenigen Prozesse, die durch ihre Prozess-ID (PID) bzw. eine Job-Spezifikation benannt wurden. Eine Signal-Spezifikation ist entweder ein Signal-Name wie SIGKILL (wobei das SIG-Präfix optional ist und nicht zwischen Groß- und Kleinschreibung unterschieden wird) oder eine Signal-Nummer. Wenn kein Signal spezifiziert wurde, dann sendet kill immer das Signal SIGTERM.
Hier einige Beispiele:
# Signal 10 (SIGUSR1) an Job Nummer 1 senden: kill -s USR1 %1 kill -s SIGUSR1 %1 kill -n 10 %1 kill -10 %1 kill -USR1 %1 kill -SIGUSR1 %1 # Die Bash sendet das Signal an alle Prozesse des Jobs. # Signal 10 (SIGUSR1) an Prozess 9253 senden: kill -s USR1 9253 kill -s SIGUSR1 9253 kill -n 10 9253 kill -10 9253 kill -USR1 9253 kill -SIGUSR1 9253
Der Exit-Status der Variante 1 von kill lautet 0, wenn mindestens ein Signal erfolgreich gesendet wurde (d.h. an einen von evtl. mehreren Prozessen eines Jobs), und 1, wenn ein Fehler, eine ungültige Option oder ein ungültiges Argument auftrat.
Die Variante 2 von kill dient zur Anzeige von Signal-Nummern und -Namen. Wenn nur das Argument -l angegeben wird, listet kill alle existierenden Signal-Namen auf, wobei diesen jeweils die Signal-Nummer vorausgeht. Der Anfang der Ausgabe sieht in etwa so aus:
1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL 5) SIGTRAP 6) SIGABRT 7) SIGBUS 8) SIGFPE 9) SIGKILL 10) SIGUSR1 11) SIGSEGV 12) SIGUSR2 ...
Wenn hinter -l weitere Argumente folgen, müssen dies entweder Signal-Spezifikationen oder Exit-Status-Werte sein. Für jedes dieser Argumente wird der zugehörige Signal-Name bzw. die korrespondierende Signal-Nummer aufgelistet. Hier ein Beispiel:
kill -l 1 USR1 5 STOP 138
# Informationen zu den Signalen 1, SIGUSR1, 5 und SIGSTOP sowie zum
# Exit-Status 138 ermitteln
#
# Ausgabe:
# HUP
# 10
# TRAP
# 19
# USR1
Variante 2 von kill liefert den Exit-Status 1, wenn mindestens ein ungültiges Argument angegeben wurde. Andernfalls lautet er 0.
Das Kommando trap hat folgende Syntax:
trap [-lp] [[Kommando] Signal-Spezifikation ...]
Das Kommando ist der Signal-Handler. Er wird durch trap mit denjenigen Signalen verbunden, die durch die hinter dem Handler angegebenen Signal-Spezifikationen benannt wurden, wobei trap dieselben Signal-Spezifikationen wie kill verwendet. Solange diese Verbindung bzw. Zuordnung gilt, wird die Shell einen nicht leeren Handler aufrufen, nachdem eines der zugehörigen Signale empfangen wurde.
Das Kommando, also der Signal-Handler, muss aus Sicht der Shell ein einzelnes Wort sein. Falls der Handler Worttrenner enthält, sind diese geeignet zu quotieren, wie folgendes Beispiel zeigt:
trap 'echo SIGUSR1' SIGUSR1
Wenn entweder das Kommando fehlt und nur eine Signal-Spezifikation folgt oder das Kommando dem Zeichen - (Minus) entspricht, dann wird jedes der spezifizierten Signale auf diejenige Einstellung zurückgesetzt, die die Shell von ihrem Eltern-Prozess erbte.
Wenn das Kommando dem leeren String entspricht, dann werden die spezifizierten Signale von der Shell ignoriert. Außerdem ruft die Shell die von ihr gestarteten Kommandos so auf, dass diese die betreffenden Signale ebenfalls standardmäßig ignorieren.
Wenn das Kommando fehlt, aber die Option -p angegeben wurde, dann werden für die als Argumente angegegebenen Signale die aktiven Traps (also Zuordnungen von Kommandos bzw. Handlern zu diesen Signalen) angezeigt. Sofern für ein als Argumente benanntes Signal kein Trap existiert, erfolgt dafür keinerlei Anzeige.
Wenn trap ganz ohne Argumente oder nur mit der Option -p aufgerufen wurde, dann wird die komplette Liste der aktiven Traps angezeigt, also inkl. jener, die ein leeres Kommando enthalten und somit das Signal ignorieren.
Die Option -l von trap bewirkt die Anzeige aller Signal-Namen und zugehörigen Nummern (analog kill -l). Argumente hinter der Option werden hierbei allerdings ignoriert.
trap unterstützt einige spezielle Signal-Spezifikationen:
EXIT | Der Handler wird beim Beenden der Shell ausgeführt. |
0 | wirkt wie EXIT |
DEBUG |
Der Handler wird jeweils vor einem der folgenden Kommandos ausgeführt:
Anmerkung: Bei der nicht-arithmetischen for-Schleife wird der DEBUG-Trap vor jedem Schleifendurchlauf aktiviert. Bei der arithmetischen Variante der for-Schleife wird der DEBUG-Trap vor jedem arithmetischen Kommando ausgeführt. |
ERR | Der Handler wird standardmäßig nach jedem einfachen Kommando ausgeführt, dessen Exit-Status ungleich 0 ist, nicht aber, wenn das fehlgeschlagene Kommando hinter while oder until folgt, im Rahmen von if oder einer UND- bzw. ODER-Liste (&& und ||) getestet oder sein Exit-Status mittels ! invertiert wird. Das sind dieselben Bedingungen wie bei der Option errexit. |
RETURN | Der Handler wird jedesmal dann ausgeführt, wenn ein mit dem Kommando source bzw. . (Punkt) eingelesenes Skript beendet wurde. Laut Manual soll ein RETURN-Trap auch beim Beenden einer Shell-Funktion ausgeführt werden. In praktischen Tests des Autors war dies aber nicht der Fall. |
Die speziellen Traps werden im Skript SCRIPTS/spezial_traps.sh demonstriert.
Den Signalen, die beim Start der Shell vom Eltern-Prozess ignoriert wurden, kann man innerhalb der Shell keine anderen Handler zuordnen.
Wenn die Shell einen Kind-Prozess kreiert, reinstalliert sie für alle Signale, deren Behandlung mit trap geändert worden war, die vom Eltern-Prozess der Shell geerbte Einstellung.
Der Exit-Status von trap lautet bei ungültiger Signal-Spezifikation 1, sonst 0.
Die Skripte
zeigen die Vererbung der Signal-Handler-Einstellungen eines Eltern-Prozesses an eine Bash. Der Eltern-Prozess wurde hier als Python- und nicht als Shell-Skript realisiert, um nicht den Restriktionen der Shell zu unterliegen, sondern flexibler die gewünschten Signal-Handler über Unix-Systemrufe installieren zu können. Python-Skripte haben den Vorteil, dass sie relativ leicht lesbar sind und im Gegensatz zum Beispiel zu C-Programmen keine explizite Übersetzung erfordern. Die Kommentare im Python-Skript erläutern jeweils den Sinn der einzelnen Anweisungen.
Als weiteres Beispiel zur Verwendung von trap sollen die Shell-Skripte
dienen. Das erste Skript startet das zweite und sorgt dafür, dass alle Ausgaben des zweiten Skripts sowohl auf die Standard-Ausgabe als auch in eine Log-Datei geschrieben werden. Das zweite Skript soll mit den Signalen SIGINT und SIGQUIT sauber beendet werden können. D.h., diese beiden Signale sind einzufangen und durch einen eigenen Handler gezielt zu behandeln. Das erste Skript muss daher dafür sorgen, dass beide Signale an das zweite Skript weitergeleitet werden.
Kommando-Index:
Kommandos:
: - leeres (wirkungsloses) Kommando
Syntax:
: [Argumente]
Das Kommando : hat selbst keine Wirkung, ist also ein leeres Kommando, dass man dort einsetzen kann, wo man syntaktisch ein Kommando benötigt, aber funktionell nichts tun will.
Beispiel:
if true
then
echo true
else
# nichts tun, daher leeres Kommando angeben
:
fi
Die optionalen Argumente von : werden expandiert, und alle spezifizierten Umlenkungen werden ausgeführt. Es wird immer der Rückgabewert 0 (also Erfolg) geliefert.
builtin: Builtin-Kommando auswählen
Bei gleichnamigen Kommandos wird das entsprechende Builtin-Kommando und kein Alias und keine Shell-Funktion ausgeführt.
Beispiel:
function cd()
{
echo "cd: $1"
# hier Builtin verwenden, um eine Endlos-Schleife zu vermeiden
builtin cd $1
}
cd /tmp
cd: Wechsel des aktuellen Verzeichnisses
Syntax:
cd [-L|-P] [Verzeichnis]
Beim Fehlen des optionalen Verzeichnis-Arguments wechselt cd ins Home-Verzeichnis des Nutzers, d.h. in das Verzeichnis, dessen absoluter Pfad in der Shell-Variablen HOME steht (also $HOME).
Wenn als Argument das Zeichen - (Minus) angegeben wird, wechselt cd zum zuvor aktuellen Verzeichnis (Inhalt der Shell-Variablen OLDPWD).
Optionen:
-L | logischer Verzeichnis-Wechsel mit Verfolgung symbolischer Links |
-P | physischer Verzeichnis-Wechsel, d.h. Verwendung der physischen Verzeichnis-Struktur statt Verfolgung symbolischer Links |
Durch Setzen der set-Option -P wird standardmäßig die physische Verzeichnis-Struktur verwendet. Anderenfalls werden im Standardfall symbolische Links verfolgt.
Nach jedem erfolgreichen Verzeichnis-Wechsel aktualisiert die Bash die Variable PWD, in die sie nach einem logischen cd den logischen Pfad und nach einem physischen cd den physischen Pfad zum neuen aktuellen Verzeichnis einträgt. Diese Variable kann der Bash-Nutzer modifizieren. Allerdings kann er damit nicht die Arbeitsweise von cd beeinflussen, da PWD dabei nicht ausgewertet wird.
Der Unterschied zwischen dem logischen und dem physischen Verzeichnis-Wechsel soll an einem Beispiel verdeutlicht werden. Wenn /usr/tmp ein symbolischer Link auf ../var/tmp ist und wir das Skript
# logischen und physischen Pfad des aktuellen Verzeichnisses anzeigen function pwd_lp() { echo -n "$(pwd -L) $(pwd -P) $(/bin/pwd) " } echo -n 'logisch/logisch: ' ; cd -L /usr/tmp/ ; pwd_lp ; cd -L .. ; pwd_lp ; echo echo -n 'logisch/physich: ' ; cd -L /usr/tmp/ ; pwd_lp ; cd -P .. ; pwd_lp ; echo echo -n 'physich/logisch: ' ; cd -P /usr/tmp/ ; pwd_lp ; cd -L .. ; pwd_lp ; echo echo -n 'physich/physich: ' ; cd -P /usr/tmp/ ; pwd_lp ; cd -P .. ; pwd_lp ; echo
ausführen, erhalten wir folgende Ausgabe:
logisch/logisch: /usr/tmp /var/tmp /var/tmp /usr /usr /usr logisch/physich: /usr/tmp /var/tmp /var/tmp /var /var /var physich/logisch: /var/tmp /var/tmp /var/tmp /var /var /var physich/physich: /var/tmp /var/tmp /var/tmp /var /var /var
Alle vier Kommando-Sequenzen des Skripts wechseln zunächst ins Verzeichnis /usr/tmp. Dadurch gelangen sie immer an dieselbe Stelle des Dateibaums, weil der symbolische Link vom Betriebssystem im Gegensatz zur Bash immer verfolgt wird. Allerdings unterscheidet sich der von der Bash gepflegte logische Pfad. Beim logischen cd lautet er /usr/tmp, beim physischen Verzeichnis-Wechsel dagegen /var/tmp.
Dies hat ggf. Auswirkungen auf den nachfolgenden Wechsel ins Eltern-Verzeichnis, der in jeder Kommando-Sequenz erfolgt. Bei einem logischen cd gelangen wir nach /usr und bei einem physischen Wechsel nach /var.
Anmerkung: Das Betriebssystem kennt lediglich den physischen Pfad. Der logische Pfad ist dagegen nur der Bash bekannt. Beim eingebauten Kommando pwd kann man sich mit der Option -L den logischen und mit -P den physischen Pfad anzeigen lassen. Das externe Kommando pwd zeigt generell den physischen Pfad an.
Bei der Beschreibung des POSIX-Modus wurde darauf hingewiesen, dass dort nicht auf den physischen Modus zurückgefallen wird, wenn beim logischen cd der aus $PWD und dem Verzeichnis-Argument gebildete Pfadname nicht existiert. Daher ergibt sich bei der Ausführung des folgenden Skripts cdtest.sh ein Unterschied zwischen nativem und POSIX-Modus:
# Demonstration des Unterschieds zwischen nativem und POSIX-Modus bei
# fehlerhaftem logischem cd
rm -rf /tmp/physisch /tmp/logisch
mkdir -p /tmp/physisch/xxx
ln -s /tmp/physisch /tmp/logisch
cd -L /tmp/logisch
pwd -P ; pwd -L; echo "$PWD"; echo
rm /tmp/logisch
cd -L xxx
pwd -P ; pwd -L; echo "$PWD"
Die Ausgabe des nativen Modus lautet:
/tmp/physisch /tmp/logisch /tmp/logisch /tmp/physisch/xxx /tmp/physisch/xxx /tmp/physisch/xxx
Beim POSIX-Modus sieht sie dagegen so aus:
/tmp/physisch /tmp/logisch /tmp/logisch cdtest.sh: line 10: cd: xxx: Datei oder Verzeichnis nicht gefunden /tmp/physisch /tmp/physisch /tmp/logisch
Nach dem logischen Wechsel zu /tmp/logisch stand das Skript physisch im Verzeichnis /tmp/physisch. Durch das anschließende Löschen des symbolischen Links /tmp/logisch gab es dieses logische Verzeichnis nicht mehr. Daher ist der nachfolgende logische Wechsel ins Unterverzeichnis xxx nur dann erfolgreich möglich, wenn die Bash auf den physischen Modus zurückfällt, denn unter /tmp/physisch existiert xxx weiterhin. Dieser Modus-Wechsel erfolgt allerdings nur im nativen Modus der Bash. Da er im POSIX-Modus unterbleibt, kommt es dort zum gezeigten Fehler.
Anmerkung: Wenn wir vor dem Kommando
cd -L xxx
mit pwd -P den physischen Pfad ausgegeben hätten, hätte die Bash im Nebeneffekt ihren logischen Pfad aktualisiert. Er hätte dann dem physischen Pfad entsprochen, weswegen cd in diesem Falle auch im POSIX-Modus erfolgreich verlaufen wäre.
Die Shell-Variable CDPATH enthält den Suchpfad für die als Argumente von cd-Kommandos angegebenen Verzeichnisse. Die Elemente von CDPATH werden durch je einen Doppelpunkt voneinander getrennt. Eine leeres Element wird als Punkt (aktuelles Verzeichnis) interpretiert.
Der vom Kommando cd tatsächlich verwendete Such-Pfad entspricht dem Inhalt von CDPATH, an den implizit ein Punkt angehängt wurde. Das aktuelle Verzeichnis wird also standardmäßig in die Suche einbezogen, auch wenn es nicht explizit in CDPATH erwähnt wird bzw. CDPATH leer ist oder nicht existiert. Wenn das Argument von cd mit einem Schrägstrich beginnt, wird CDPATH allerdings nicht berücksichtigt.
Nach einem erfolgreichen Verzeichnis-Wechsel, der unter Verwendung eines explizit in CDPATH benannten Verzeichnisses oder durch das Argument - erfolgte, gibt cd den absoluten Pfad des neuen Verzeichnisses aus. Diese Ausgabe unterbleibt also, wenn der implizit angehängte Punkt verwendet wurde.
Bei gesetzter shopt-Option cdspell werden in einer interaktiven Bash kleinere Tippfehler im Argument von cd automatisch korrigiert. Anschließend wird das korrigierte Argument ausgegeben und das Kommando ohne Rückfrage ausgeführt.
Wenn die shopt-Option cdable_vars aktiviert wurde, dann wertet die Bash ein Argument, das kein gültiges Verzeichnis ist, als Name einer Variablen. Sofern sie existiert und ihr Wert der absolute oder relative Name eines existierenden Verzeichnisses ist, dann wechselt cd stillschweigend in dieses Verzeichnis.
Weitere Beispiele für cd:
# Man kann bequem zwischen zwei Verzeichnissen pendeln: cd # Wechsel zu $HOME cd /tmp # Wechsel zu /tmp cd - # Wechsel zu $OLDPWD, hier also $HOME cd - # Wechsel zu /tmp # Nutzung von CDPATH: # Wir nehmen an, dass es im Home-Verzeichnis des Nutzers sowie # unter /tmp ein Verzeichnis abc gibt, wogegen das Verzeichnis # xyz nur im Home-Verzeichnis vorkommt. CDPATH=/tmp # effektiver Such-Pfad: /tmp und . cd abc # Wechsel zu /tmp/abc cd # Wechsel zu $HOME cd xyz # Wechsel zu ./xyz CDPATH=:/tmp # effektiver Such-Pfad: . und /tmp cd # Wechsel zu $HOME cd abc # Wechsel zu ./abc # Nutzung der Option cdable_vars: # Option aktivieren shopt -s cdable_vars # den Verzeichnis-Namen abc in der Variablen fgh speichern fgh=abc # Wechsel ins Verzeichnis abc, sofern kein Verzeichnis fgh existiert cd fgh
command: Kommando (eingebaut oder extern) auswählen
Bei gleichnamigen Kommandos wird ein eingebautes oder externes Kommando und kein Alias und keine Shell-Funktion ausgeführt. Eingebaute Kommandos haben Vorrang vor gleichnamigen externen Kommandos.
Beispiel:
function cp() { echo 'cp nicht verfügbar!!' } # Kommando cp ruft die Funktion cp cp file file.save # command cp ruft das (externe) Kommando cp command cp file file.save
Die Kommandos declare und typeset sind Synonyme, wobei typeset mittlerweile als veraltet gilt. Mit beiden kann man Variablen deklarieren und ihnen Attribute zuordnen.
Syntax:
declare [-afFirtx] [-p] [Variablen-Name[=Wert] ...]
Optionen:
-a | jeder angegebene Variablen-Name ist ein Array |
-f | benutze nur Funktionsnamen |
-F | keine Funktionsdefinitionen anzeigen, nur Funktionsnamen und Attribute |
-i | Variable ist vom Typ Integer; bei Zuweisungen erfolgt automatisch eine arithmetische Auswertung des zugewiesenen Wertes |
-p | Anzeige von Attributen und Werten aller angegebenen Namen |
-r | Variable wird als nur lesbar (readonly) deklariert; nachfolgende Zuweisungen/Löschungen werden unterbunden |
-t | jeder benannten Funktion wird das Trace-Attribut gegeben; solche Funktionen erben den DEBUG-Trap von der rufenden Shell |
-x | markiert Namen für den Export |
Wenn man den obigen Optionen ein + an Stelle eines - voranstellt, werden die entsprechenden Attribute abgeschaltet, was aber nicht in allen Fällen funktioniert. So kann man mit +a ein existierendes Feld nicht zerstören. Mit +r lässt sich das readonly-Attribute auch nicht wieder entfernen.
Wenn man declare in einer Funktion nutzt, legt man damit wie mit local lokale Variablen an. Wenn dem Variablen-Namen ein Ausdruck =Wert folgt, wird der Variablen der angegebene Wert zugewiesen.
Beispiele:
declare # alle Variablen und Werte anzeigen declare -p # dito declare -p anz # Attribute und Wert von Variable/Feld anz anzeigen declare -pix # alle Integer- und exportierten Variablen anzeigen declare -pr # alle Readonly-Variablen anzeigen declare -pa # alle Array-Variablen anzeigen declare -f # alle Funktionen mit Definition (Körper) auflisten declare -f abc # Funktion abc mit Definition (Körper) auflisten declare -fF # alle Funktionen ohne Definition auflisten # lokale Variable in einer Funktion anlegen und initialisieren function f() { declare a=xyz } # Integer-Variable deklarieren declare -i anz=5 anz=anz+1 echo "$anz" # Ausgabe: 6 # Integer-Attribut löschen declare +i anz anz=anz+1 echo "$anz" # Ausgabe: anz+1 # Readonly-Variable deklarieren declare -r anz unset anz # wird abgewiesen
dirs: Anzeige oder Löschung des mit pushd und popd manipulierten Verzeichnis-Stacks
Syntax:
dirs [-clpv] [+n] [-n]
Beim Aufruf des Kommandos ohne Optionen wird der aktuelle Verzeichnis-Stack in Kurzform ausgegeben. Alle Verzeichnisse des Stacks erscheinen dabei auf einer einzigen Zeile und sind durch je ein Leerzeichen voneinander getrennt. Verzeichnisse werdem mit dem Kommando pushd zum Stack hinzugefügt und mit popd wieder gelöscht.
Die Elemente des Stacks sind von 0 beginnend fortlaufend durchnummeriert, so dass man sie über diese Nummer bzw. diesen Index eindeutig ansprechen kann. Ein Argument der Form +n zeigt den Stack-Eintrag mit Index n an.
Hat das Argument dagegen die Form -n, dann wird vom Ende des Stacks her gezählt. Der Index ergibt sich hierbei durch Subtraktion der angegebenen Zahl n vom höchsten Index des Stacks.
Optionen:
-c | löscht den gesamten Verzeichnis-Stack |
-l | erzeugt eine Langform der Ausgabe, bei der im Gegensatz zur Kurzform das Home-Verzeichnis nicht durch eine Tilde symbolisiert wird |
-p | jedes Element des Stacks wird auf einer separaten Zeile ausgegeben |
-v | wie Option p, wobei zusätzlich vor jedem Element dessen Index im Stack angegeben wird |
Beispiele:
# Anzeige des Verzeichnis-Stacks im kurzen Format dirs # zeilenweise Anzeige des Verzeichnis-Stacks im langen Format mit Indexen dirs -l -v # Im Gegensatz zu tcsh und zsh funktioniert die Zusammenfassung # von Optionen (z.B. dirs -lv) nicht. # Verzeichnis-Stack komplett löschen dirs -c # Stack-Element mit Index 2 (drittes Element) anzeigen dirs +2 # erstes Stack-Element anzeigen dirs +0 # letztes Stack-Element anzeigen dirs -0
Das Kommando echo gibt der Reihe nach alle seine Argumente getrennt durch je ein Leerzeichen auf die Standard-Ausgabe aus. Standardmäßig wird abschließend noch ein Newline-Zeichen ausgegeben.
Syntax:
echo [-neE] [Argument ...]
Optionen:
-e | Interpretation der unten folgenden Escape-Folgen aktivieren |
-E | Interpretation der unten folgenden Escape-Folgen deaktivieren |
-n | Unterdrückung des abschließenden Newline-Zeichens |
Unterstützte Escape-Folgen:
\a | Alert (Bell) |
\b | Backspace |
\c | Unterdrückung aller nachfolgenden Zeichen (inkl. Newline) |
\e | Escape |
\f | Formfeed |
\n | Newline |
\r | Carriage Return |
\t | Horizontal-Tabulator |
\v | Vertikal-Tabulator |
\\ | Backslash |
\’ | Apostroph |
\0nnn | Oktalzahl (0 bis 3 Ziffern) |
\nnn | Oktalzahl (1 bis 3 Ziffern) |
\xHH | Hexadezimal-Zahl (1 oder 2 Ziffern) |
Wenn die shopt-Option xpg_echo gesetzt ist, werden die Escape-Folgen standardmäßig (also ohne Option -e) interpretiert. Bei ausgeschalteter Option xpg_echo werden sie standardmäßig nicht interpretiert.
Beispiele:
# Interpretation der Escape-Folgen aktivieren shopt -s xpg_echo # Ausgabe der Zeichenfolge a Backspace Newline c echo -n 'a\'b' c' # Interpretation der Escape-Folgen deaktivieren shopt -u xpg_echo # Ausgabe der Zeichenfolge a Backslash b Newline c echo -n 'a\'b' c' # Ausgabe von abc ohne folgendes Newline echo -e 'abc\cxyz'
enable: Builtin-Kommando aktivieren/deaktivieren
Wenn man ein Builtin-Kommando deaktiviert, kann man ein gleichnamiges externes Kommando ohne Angabe des vollen Pfades aufrufen.
Mit enable kann man auch weitere eingebaute Kommandos aus sog. Shared Objects dynamisch in einen Bash-Prozess nachladen und geladene Kommandos wieder entfernen. Diese nachladbaren Kommandos müssen geeignet (also unter Beachtung bestimmter Konventionen) implementiert sein, so dass sie die Bash auch erfolgreich laden und nutzen kann.
Die Bash-Distribution enthält verschiedene Beispiele für ladbare Builtin-Kommandos, die in der Sprache C implementiert sind. Nachdem sie geeignet mit einem C-Compiler in ein Shared Object übersetzt wurden, können sie von der Bash geladen werden. Da die Shared Objects Maschinencode enthalten, sind sie nicht portabel auf verschiedenen Plattformen nutzbar. Für unterschiedliche Plattformen sind also aus demselben C-Quelltext verschiedene plattform-spezifische Shared Objects zu erstellen.
Syntax:
enable [-adnps] [-f Dateiname] [Builtin-Name ...]
Optionen:
-a | Liste aller Builtin-Kommandos ausgeben und pro Kommando anzeigen, ob es aktiviert oder deaktiviert ist |
-d | Löschen eines mit -f geladenen Builtin-Kommandos |
-f Dateiname | dynamisches Laden eines Builtin-Kommandos aus der Datei Dateiname, bei der es sich um ein Shared Object handeln muss |
-n | ohne Argumente: nur deaktivierte Builtin-Kommandos anzeigen
mit Argumenten: angegebene Builtin-Kommandos deaktivieren |
-p | Anzeige einer Liste von Builtin-Kommandos
Wenn keine anderen Optionen aktiv sind, werden nur die aktivierten Kommandos ausgegeben. Die Kommando-Liste wird auch dann angezeigt, wenn kein Builtin-Name als Argument angegeben wurde. |
-s | nur die POSIX-Spezial-Builtin-Kommandos anzeigen |
Der Exit-Status von enable ist 0, es sei denn, einer der angegebenen Builtin-Namen ist kein Name eines existierenden Builtin-Kommandos oder es trat ein Fehler beim dynamischen Laden eines Kommandos auf.
Beispiel:
type printf # printf is a shell builtin # Builtin printf deaktivieren enable -n printf type printf # printf is /usr/bin/printf # Builtin printf aktivieren enable printf type printf # printf is a shell builtin
eval: nochmalige Auswertung einer Kommandozeile:
Syntax:
eval [Argument ...]
Die Argumente werden zu einem einzigen Kommando zusammengefügt. Dieses wird dann durch die Shell interpretiert. Sein Exit-Status wird als Exit-Status von eval geliefert. Wenn keine oder nur leere Argumente angegeben werden, dann ist der Exit-Status 0.
command='echo $name' name=Max $command # Ausgabe: $command eval $command # Ausgabe: Max
getopts: Auswertung von Kommandozeilen-Optionen
Syntax:
getopts Options-String Options-Variable [Argumente]
Dieses Kommando dient dazu, die explizit angegebenen Argumente oder, falls diese fehlen, implizit die Positionsparameter hinsichtlich der darin enthaltenen Kommandozeilen-Optionen zu analysieren. Es liefert den Exit-Status 0, wenn eine (spezifizierte oder nicht spezifizierte) Option gefunden wurde, und 1, wenn das Ende der Optionen erreicht wurde oder ein Fehler auftrat.
Hinweis: Unter dem Namen getopt existiert ein externes Kommando zur Analyse von Kommandozeilen-Optionen, das sich in seinen Details von getopts unterscheidet und hier nicht besprochen werden soll. Im Unterschied zu getopts kann man mit getopt auch die langen Optionen auswerten, bei denen der lange Options-Name dem Präfix -- folgt.
Wir wollen am folgenden Beispiel, das unter SCRIPTS/getopts.sh zu finden ist, das Wesentliche zeigen, wobei der vorangehende Kommentar nicht alle der recht umfangreichen Details dieses Kommandos diskutiert:
#!/bin/sh # # Optionsauswertung mit getopts # # 5.12.2007 # Ein Doppelpunkt am Anfang des Optionen-Strings verhindert Fehlermeldungen # der Bash. # # Akzeptieren wollen wir die Optionen p und h. Option p # hat (im Gegensatz zu Option h) ein Argument, daher folgt dem # Optionsbuchstaben im Options-String ein Doppelpunkt. # # Die Variable option bekommt der Reihe nach die erkannten Optionen # zugewiesen. Die Shell-Variable OPTIND, die beim Start der Shell bzw. # eines Shell-Skripts mit 1 initialisiert wird, gibt den Index des nächsten # Arguments an, das von getopts zu bearbeiten ist. # # Eine ungültige Option erkennen wir daran, dass option den Wert # ? hat. Ein fehlendes Argument wird hier durch den Wert : # signalisiert, da wir durch den Doppelpunkt am Anfang des Optionen-Strings # Fehlermeldungen unterdrücken. Täten wir dies nicht, würde der Variablen # option ein ? zugewiesen werden. Außerdem würde die Bash dann # eine Fehlermeldung ausgeben und die Variable OPTARG löschen. # # Das Argument einer gültigen Option finden wir in der Shell-Variablen # OPTARG. while getopts :p:h option do case "$option" in p) echo "Port: $OPTARG" ;; h) echo "help" ;; \?) echo "Ungültige Option: $OPTARG" ;; :) echo "Fehlendes Argument bei Option $OPTARG" ;; esac done # alle Optionen aus der Argumenteliste streichen shift $((OPTIND - 1)) # Kontrollausgabe der Argumentanzahl und -liste echo $# echo "$*"
hash: Pflege der Hash-Tabelle der Bash
Syntax:
hash [-lr] [-p Dateiname] [-dt] [Kommando-Name ...]
Wenn man das Kommando ohne Optionen ruft, wird für jeden angegebenen Kommando-Namen der absolute Pfadname ermittelt und in die Hash-Tabelle eingetragen, indem die Bash die in der Variablen PATH aufgeführten Verzeichnisse durchsucht. Ein evtl. veralteter Eintrag, dessen neuer Pfad nicht ermittelt werden kann, wird in der Hash-Tabelle belassen. Mit der Option -d kann man erzwingen, dass der veraltete Eintrag gelöscht wird.
Optionen:
-d | aktualisiert die Hash-Tabellen-Einträge der genannten Kommandos bzw. löscht sie, wenn das Kommando im Pfad nicht gefunden werden kann |
-l | Ausgabe der kompletten Hash-Tabelle (in einer wiedereinlesbaren Form) |
-p | unterdrückt die Pfad-Suche und übernimmt den angegebenen Dateinamen ungeprüft als absoluten Pfadnamen des Kommandos |
-r | löscht die existierende Hash-Tabelle komplett |
-t | Ausgabe des absoluten Pfadnamens jedes angegebenen Kommandos laut seinem Eintrag in der Hash-Tabelle |
Um sich die Hash-Tabelle anzeigen zu lassen, muss man hash entweder komplett ohne Argumente und Optionen oder nur mit der Option -l aufrufen. Das Format der Ausgabe unterscheidet sich zwischen beiden Varianten. Die erste Variante gibt noch die Anzahl der Treffer (hits) aus, die besagt, wie oft jeder Hash-Tabelleneintrag zum Kommando-Aufruf verwendet wurde, wobei hier auch Zugriffe gezählt werden, die auf Grund eines inaktuellen Hash-Eintrags zu einem Fehler führten.
Der Exit-Status ist 1, wenn eine ungültige Option angegeben oder ein Kommando nicht gefunden wurde. In allen anderen Fällen ist er 0.
Beispiele:
# Hash-Einträge für die Kommandos awk und python anlegen # oder aktualisieren, ohne Einträge zu streichen, die # nicht aktualisierbar sind hash awk python # Hash-Tabelle komplett ausgeben hash # Hash-Tabelle komplett in wiedereinlesbarer Form ausgeben hash -l # Hash-Tabelle komplett löschen hash -r # Hash-Tabellen-Eintrag erzeugen oder aktualisieren: # /bin/xyz wird als absoluter Pfad von Kommando xyz vermerkt hash -p /bin/xyz xyz # absoluten Pfad des Kommandos xyz laut Hash-Tabellen-Eintrag ausgeben hash -t xyz # Hash-Tabellen-Eintrag für Kommando xyz aktualisieren lassen # und im Fehlerfalle löschen hash -d xyz
Mit diesem Kommando kann man sich Hilfetexte zu den eingebauten Kommandos anzeigen lassen.
Syntax:
help [-s] [Muster]
Wenn das Muster angegeben wird, zeigt die Bash Hilfe-Informationen zu allen Kommandos an, die auf dieses Muster passen. Sonst wird ein Hilfe-Text zu allen eingebauten Kommandos und Steuer-Strukturen ausgegeben. Als Muster kann man Pfadnamens-Muster sowie die Anfänge der Kommandonamen verwenden.
Die Option -s beschränkt die Anzeige auf die kurze Aufruf-Syntax (Synopsis).
Der Exit-Status ist 0, es sei denn, es wurde ein Muster angegeben, auf das kein Kommando passt.
Beispiele:
help cd # Hilfe-Texte für Kommandos anzeigen, die mit cd beginnen help -s p # kurze Hilfe-Texte für alle Kommandos ausgeben, die mit p beginnen help -s 'p*d' # kurze Hilfe-Texte für alle Kommandos ausgeben, die auf das Muster p*d passen
popd: Manipulation des Verzeichnis-Stacks; Pendant zu pushd
Syntax:
popd [-n] [+x] [-x]
Standardmäßig (ohne Argument oder Option) wird das oberste Element vom Verzeichnis-Stack entfernt und ein cd zum neuen obersten Element des Verzeichnis-Stacks ausgeführt. Die Option -n unterdrückt den Verzeichnis-Wechsel.
Ein Argument der Form +x entfernt das x-te Element von links im Verzeichnis-Stack (vom Kommando dirs ausgegebene Liste), wobei die Zählung bei 0 beginnt. Ein Argument der Form -x entfernt das x-te Element von rechts im Verzeichnis-Stack, wobei die Zählung ebenfalls bei 0 beginnt.
Beispiele:
# Verzeichnis-Stack: /tmp /etc /boot ~ popd # entfernt /tmp vom Stack und wechselt zu /etc # Verzeichnis-Stack: /tmp /etc /boot ~ popd -n # entfernt /tmp, führt aber kein cd aus # Verzeichnis-Stack: /tmp /etc /boot ~ popd -0 # entfernt ~ # Verzeichnis-Stack: /tmp /etc /boot ~ popd +0 # entfernt /tmp
printf: Alternative zum externen Kommando printf
Syntax:
printf Format [Argumente]
Die Argumente werden formatiert ausgegeben, wobei die Formatierung durch den Format-String Format gesteuert wird. Details dazu beschreibt das Manual des externen Kommandos printf.
Der Format-String wird ggf. wiederholt benutzt, um alle Argumente zu konsumieren. Wenn es nicht genug Argumente gibt, dann werden die fehlenden je nach Kontext als leerer String bzw. als Zahl 0 angenommen.
Beispiel:
maxlen=4 fmt="%-${maxlen}s: %s\n" printf "$fmt" otto /bin/bash # Ausgabe: otto: /bin/bash printf "$fmt" hot /bin/tcsh # Ausgabe: hot : /bin/tcsh printf '%d %d' 34 # Ausgabe: 34 0 printf '%d ' 34 17 81 # Ausgabe: 34 17 81
pushd: Manipulation des Verzeichnis-Stacks; Pendant zu popd
Syntax:
pushd [-n] [Verzeichnis] pushd [-n] [+x] [-x]
Format 1 des Kommandos fügt das genannte Verzeichnis zum Verzeichnis-Stack hinzu und wechselt standardmäßig mit cd in dieses Verzeichnis. Durch die Option -n kann man den Verzeichnis-Wechsel unterdrücken.
Wenn man das Verzeichnis-Argument weglässt, werden die beiden obersten Stack-Elemente ausgetauscht.
Ohne Option -n wird bei pushd generell nach jeder Stack-Manipulation in das Verzeichnis gewechselt, das (neu) an der Stack-Spitze steht.
Ein Argument der Form +x bewirkt die Rotation des Stacks, so dass das x-te Stack-Element von links (in der vom Kommando dirs ausgegebenen Liste) an die Stack-Spitze gelangt, wobei die Zählung bei 0 beginnt. Ein Argument der Form -x bewirkt die Rotation des Stacks, so dass das x-te Stack-Element von rechts an die Stack-Spitze gelangt, wobei die Zählung bei 0 beginnt.
Beispiele:
# /tmp als neues oberstes Element zum Verzeichnis-Stack # hinzufügen und cd /tmp ausführen pushd /tmp # /tmp als neues oberstes Element zum Verzeichnis-Stack # hinzufügen, aber kein cd /tmp ausführen pushd -n /tmp # Verzeichnis-Stack: /tmp /etc /boot ~ pushd +2 # rotiert zu: /boot ~ /tmp /etc # Verzeichnis-Stack: /tmp /etc /boot ~ pushd -0 # rotiert zu: ~ /tmp /etc /boot
read: Einlesen einer Zeile mit stark erweiterten Möglichkeiten gegenüber der Bourne-Shell
Syntax:
read [-ers] [-u Deskriptor] [-t Timeout] [-a Feld-Name] [-p Prompt] [-n Zeichen-Anzahl] [-d Trenner] [Variablen-Name ...]
Wenn man bei read keine Variablen-Namen als Argumente angibt, wird die komplette eingelesene Zeile der Variablen REPLY zugewiesen.
Ansonsten wird das erste Wort der Zeile der ersten Variablen, das zweite Wort der zweiten Variablen usw. zugewiesen. Gibt es mehr Wörter als Variablen, dann bekommt die letzte Variable den kompletten Zeilenrest zugewiesen. Gibt es weniger Wörter als Variablen, dann werden die überzähligen Variablen mit der leeren Zeichenkette gefüllt.
Worttrenner ist der Inhalt der Variablen IFS.
Ein Backslash in der Eingabe-Zeile hebt die Sonderbedeutung des folgenden Zeichens auf, sofern nicht die Option -r angegeben wurde. Die Sequenz Backslash Newline bewirkt, dass die folgende Zeile als Fortsetzung der aktuellen Zeile betrachtet wird. Somit kann sich eine logische Zeile auf mehrere physische Zeilen erstrecken.
Optionen:
-a Feld-Name | die einzelnen Wörter in das Feld Feld-Name einlesen (ab Index 0) |
-d Trenner | erstes Zeichen von Trenner wird als Zeilentrenner verwendet |
-e | verwende die Readline-Bibliothek, falls von einem Terminal gelesen wird |
-n Zeichen-Anzahl | lies maximal Zeichen-Anzahl Zeichen |
-p Prompt | gibt Prompt als Prompt-String auf stderr aus, falls von einem Terminal gelesen wird |
-r | rohes (raw) Lesen; Backslash wird als normales Zeichen ohne Sonderbedeutung betrachtet; Backslash Newline kann nicht zur Realisierung von Fortsetzungszeilen verwendet werden |
-s | silent mode; kein Echo beim Lesen von einem Terminal |
-t Timeout | Timeout-Einstellung; falls der Lese-Vorgang von einer Pipe oder einem Terminal nicht nach Timeout Sekunden abgeschlossen ist, kehrt read mit einem Fehler zurück; die Variablen, die mit den gelesenen Werte gefüllt werden sollten, bleiben dann unverändert |
-u Deskriptor | lies von Deskriptor Deskriptor |
Beispiel:
read -p 'Zahlen eingeben: ' -a array echo "${array[@]}"
Achtung: Konstrukte der Form
echo xxx | read a
haben nicht den Effekt, den man im allgemeinen erwarten würde. Durch die Pipe führt die Bash das Kommando read in einem Sub-Prozess aus. Dort liest read die Ausgabe von echo (also das Wort xxx) ein und speichert sie in der Variablen REPLY. Diese Variable existiert aber nur im Sub-Prozess, nicht in der aktiven Shell, die die Pipe eingerichtet hat. Somit stehen die von read gelesenen Werte in der aktuellen Shell nicht zur Verfügung. Nach Beendigung der Pipe gibt es den Sub-Prozess auch nicht mehr.
Statt einer Pipe könnte man eine benannte Pipe (FIFO) verwenden, um dieses Problem zu lösen. Eine Lösung könnte in etwa so aussehen, wobei auf Fehlerbehandlung und sichere Erstellung der benannten Pipe verzichtet wurde, was sich in echten Skripten nicht empfiehlt:
fifo=/tmp/fifo [[ -e $fifo ]] || mkfifo $fifo echo -e 'xxx\nyyy\nzzz' &>/tmp/fifo & while read do echo $REPLY done < $fifo
Hier wird die benannte Pipe /tmp/fifo eingerichtet, sofern sie noch nicht existiert. Anschließend starten wir das Kommando echo im Hintergrund, wobei wir stdout und stderr in den FIFO umlenken. Die anschließende while-Schleife liest dann alle Ausgaben, die echo in den FIFO schrieb, aus dem FIFO aus.
Wichtig ist, die Eingabe-Umlenkung für die Schleife und nicht für read einzurichten. Die Schleife
while read < /tmp/fifo do echo $REPLY done
würde nur die erste Zeile aus dem FIFO lesen, weil read danach den FIFO schließt, wodurch der Schreib-Prozess beendet wird. Das erneute Öffnen beim folgenden Schleifendurchlauf liefert keine Daten, weil es keinen Schreib-Prozess mehr gibt. Somit wartet read vergebens und potentiell unendlich auf zu lesende Daten.
Über die Umgebungsvariable TMOUT kann man einen Timeout in Sekunden einstellen, der an folgenden Stellen wirkt, wenn dieser Variablen ein Wert größer Null zugewiesen wurde:
. oder source: Skript in der aktuellen Shell ausführen
Die Kommandos source und . sind Synonyme. In der alten Bourne-Shell war nur . verfügbar. Bei der Bash sollte man stets source verwenden.
Syntax:
source Dateiname [Argumente] . Dateiname [Argumente]
Die angegebene Datei wird eingelesen und in der aktuellen Shell-Umgebung ausgeführt. Der Exit-Status entspricht dem Exit-Status des letzten ausgeführten Kommandos. Wird kein Kommando ausgeführt, dann ist der Exit-Status 0. Wenn die Datei nicht existiert oder nicht lesbar ist, dann ist der Exit-Status 1.
Das eingelesene Skript kann vorzeitig durch das eingebaute Kommando return beendet werden, wenn dieses außerhalb einer Funktion steht. Ein optionales numerisches Argument von return legt den Exit-Status von source fest. Fehlt dieses Argument, dann entspricht der Exit-Status von source dem Exit-Status des zuletzt ausgeführten Kommandos der eingelesenen Datei.
Wenn man z.B. das folgende Skript mit source einliest, so wird es kurz nach dem Start beendet, sofern die Umgebungsvariable OSTYPE den Wert cygwin hat. Somit kann man verhindern, dass bestimmte Skripte unter der Cygwin-Umgebung zur Ausführung kommen, was u.a. für .bashrc sinnvoll sein kann.
#!/bin/sh [[ $OSTYPE == cygwin ]] && return echo kein CYGWIN
Anmerkung: Eine nicht-interaktive Bash, die unter dem Namen sh aufgerufen wurde und daher im POSIX-Modus läuft, wird sofort beendet, wenn der bei source angegebene Dateiname nicht existiert oder die Datei nicht lesbar ist. Hier kann man sich also eine Fehlerbehandlung sparen:
#!/bin/sh
source datei
# hier kann man sicher sein, dass datei eingelesen wurde
Wenn man dieses Verhalten nicht möchte, sollte man den POSIX-Modus durch set +o posix verlassen.
Wenn Dateiname kein absoluter Pfadname ist, wird im PATH nach dem Namen gesucht. Die Datei muss dazu nicht ausführbar sein.
Wenn Argumente angegeben sind, werden diese während der Ausführung der eingelesenen Datei als Positionsparameter gesetzt. Ansonsten bleiben die aktuellen Positionsparameter der Shell erhalten.
Beispiele:
# Einbindung gemeinsamer Funktionen source /usr/local/bin/common_functions.sh # Argumentübergabe an Positionsparameter $1 ... $n ist möglich source /usr/local/bin/common_functions.sh arg1 arg2 # Variable XYZ durch ein via source eingelesenes Skript setzen # Skript erstellen echo 'XYZ=123' > source_script.sh echo ${XYZ:-leer} # Skript in der aktuellen Shell ausführen source_script.sh echo ${XYZ:-leer}
test: Überprüfung von Datei-Attributen sowie Durchführung von arithmetischen und String-Vergleichen
Alternativ-Name: [
Bei Verwendung des Alternativ-Namens ist der Test-Ausdruck durch das Argument (Wort) ] abzuschließen.
Neben dieser alten Form des Tests existiert die weiter oben beschriebene neue Form, bei der die Test-Ausdrücke in [[ und ]] eingeschlossen werden: [[ Ausdruck ]].
Beide Test-Formen unterstützen dieselbe Menge von Bedingungs-Ausdrücken, unterscheiden sich aber in Details der Ausdrucks-Auswertung sowie der Menge der verfügbaren Operatoren.
Test-Ausdrücke können bei test durch folgende Operatoren kombiniert werden, die hier fallend nach ihrem Vorrang angegeben sind:
! Ausdruck
- logische Negation von Ausdruck
( Ausdruck )
- gibt den Wert von Ausdruck zurück
- kann zur Veränderung des normalen Operator-Vorrangs genutzt werden
Ausdruck -a Ausdruck
- logisches UND
- wahr, wenn beides Ausdrücke wahr sind
Ausdruck -o Ausdruck
- logisches ODER
- wahr, wenn mindestens einer der beiden Ausdrücke wahr ist
Achtung: Die runden Klammern müssen hier (im Gegensatz zu [[) quotiert werden, da sie die Shell als Zeichen mit Sonderbedeutung (Metazeichen) angesehen werden. Hier ein Beispiel:
[ \( ${name:0:1} == Z -o ${name:0:1} == O \) -a \( ${#name} -gt 3 \) ] && \ echo "$name beginnt mit Z oder O und hat eine Länge größer 3"
Bei test hängt die Auswertung der Test-Ausdrücke von der Anzahl der Argumente ab:
0 Argumente | Der Ausdruck ist falsch. |
1 Argument | Der Ausdruck ist genau dann wahr, wenn das Argument nicht leer (also kein leerer String) ist. |
2 Argumente |
Wenn das erste Argument ein ! ist, ist der Ausdruck genau dann
wahr, wenn das zweite Argument nicht leer ist.
Wenn das erste Argument ein unärer Test-Operator eines Bedingungs-Ausdrucks ist, dann ist der Ausdruck wahr, wenn der Bedingungs-Ausdruck wahr ist. Wenn das erste Argument kein gültiger unärer Test-Operator ist, dann ist der Ausdruck falsch. |
3 Argumente |
Wenn das zweite Argument ein binärer Test-Operator eines Bedingungs-Ausdrucks
ist, dann entspricht das Ergebnis des Ausdrucks dem Ergebnis des binären
Test-Operators, wobei dieser das erste und dritte Argument als Operanden
verwendet.
Wenn das erste Argument ein ! ist, dann entspricht das Ergebnis dem negierten Ergebnis des Tests, der durch das zweite und dritte Argument beschrieben wird. Wenn das erste Argument eine ( und das dritte Argument eine ) ist, dann entspricht das Ergebnis dem Ergebnis des Tests, der durch das zweite Argument beschrieben wird. In allen anderen Fällen ist das Ergebnis falsch. Die Operatoren -a und -o werden hier als binäre Operatoren betrachtet. |
4 Argumente |
Wenn das erste Argumente ein ! ist, dann entspricht das
Ergebnis der Negation des Tests, der durch die folgenden 3 Argumente
beschrieben wird.
Anderenfalls wird der Ausdruck unter Beachtung der obigen Vorrang-Regeln ausgewertet. |
5 Argumente | Der Ausdruck wird unter Beachtung der obigen Vorrang-Regeln ausgewertet. |
type: Typ eines Kommandos feststellen
Dieses Kommando ist eine inhaltliche Alternative zum eingebauten Kommando which der tcsh oder zsh, wobei letztere das Kommando type auch kennt.
Syntax:
type [-aftpP] Name [Name ...]
Das Kommando type ermittelt, wie die angegebenen Namen interpretiert würden, wenn man sie als Kommandonamen verwenden würde.
Optionen:
-a | Ausgabe aller Stellen, an denen das Kommando vorkommt, also z.B. als Alias, Funktion und externes Kommando, wobei Aliase und Funktionen nur dann gesucht werden, wenn die Option -p nicht aktiv ist |
-f | unterdrückt das Suchen von Shell-Funktionen (wie beim Builtin-Kommando command) |
-p | Ausgabe des Namens der Datei, die ausgeführt würde, sofern es sich bei dem betreffenden Namen um ein externes Kommando handelt, oder keine Ausgabe, falls es sich um kein externes Kommando handelt |
-P | erzwingt eine Suche nach dem Kommando im Pfad ($PATH) |
-t | Angabe des Kommando-Typs durch einen der Strings alias,
keyword, function, builtin oder
file.
Ein unbekannter Name erzeugt keine Ausgabe, dafür aber einen Exit-Status von 1. |
Wenn ein Kommando in der Hash-Tabelle eingetragen ist, geben die Optionen -p und -P den in der Tabelle gespeicherten Pfad aus, d.h., die in PATH gespeicherten Verzeichnisse werden nicht durchsucht.
Beispiel, in dem die möglichen Ausgaben unter den Kommandos dargestellt sind:
type l psg wc if pwd l is aliased to `ls -l' psg is a function psg () { psg_intern awk "/$1/" } wc is /usr/bin/wc if is a shell keyword pwd is a shell builtin type -a ls ls is a function ls () { /bin/ls --color=never "$@" } ls is /bin/ls type -p ls # keine Ausgabe, das ls eine Funktion und kein # externes Kommando ist type -P ls /bin/ls # Wegen der erzwungenenen Pfad-Suche bei Option -P # wird das externe Kommando ls gefunden, obwohl es eine # gleichnamige Funktion gibt.
Eine interaktive Bash zeigt den primären Prompt PS1 an, wenn sie zum Einlesen eines Kommandos bereit ist. Anschließend wartet sie auf die Eingabe. Mit der Umgebungsvariablen TMOUT kann man hierfür einen Timeout festlegen. Durch den sekundären Prompt PS2 signalisiert die Shell, dass das eingegebene Kommando noch unvollständig ist und daher weitere Eingaben benötigt werden.
Die als Werte dieser beiden Prompt-Variablen PS1 und PS2 gespeicherten Prompt-Strings können die nachfolgend aufgeführten Spezialzeichen enthalten, die durch einen vorangestellten Backslash gekennzeichnet werden:
\a | ASCII-Zeichen 7 (Alert bzw. Bell) |
\d | Datum im Format "Wochentag Monat Datum", z.B. So Jan 08 |
\D{Format} | Resultat der Bibliotheks-Funktion strftime(3), der die
Angabe Format übergeben wurde
Ein leerer Format-String resultiert in einer Locale-spezifischen Zeitdarstellung, z.B. 17:36:44. |
\e | Escape-Zeichen (Oktalwert 033) |
\h | Host-Name bis zum ersten Punkt |
\H | kompletter Host-Name |
\j | Anzahl der aktuell von der Shell verwalteten Jobs |
\l | Basis-Geräte-Name des Terminals der Shell, z.B. 4 beim Gerät /dev/pts/4 |
\n | Newline-Zeichen |
\r | Carriage-Return-Zeichen |
\s | Name der Shell, d.h. der Basisname des Wertes des Parameters $0 |
\t | aktuelle Zeit im 24-Stunden-Format HH:MM:SS |
\T | aktuelle Zeit im 12-Stunden-Format HH:MM:SS |
\@ | aktuelle Zeit im 12-Stunden-Format HH:MM |
\A | aktuelle Zeit im 24-Stunden-Format HH:MM |
\u | Nutzername des aktuellen Benutzers |
\v | Bash-Version, z.B. 3.00 |
\V | Bash-Version und Patch-Level, z.B. 3.00.16 |
\w | aktuelles Verzeichnis, wobei die Tilde das Home-Verzeichnis abkürzt |
\W | Basisname des aktuellen Verzeichnisses, wobei die Tilde das Home-Verzeichnis abkürzt |
\! | History-Nummer des einzugebenden Kommandos |
\# | Kommando-Nummer des einzugebenden Kommandos |
\$ | das Zeichen #, wenn die effektive UID den Wert 0 hat, sonst das Zeichen $ |
\nnn | Zeichen mit dem Oktalwert nnn |
\\ | Backslash |
\[ | Beginn einer Folge nicht druckbarer Zeichen, die benutzt werden kann, um Terminal-Steuer-Sequenzen in den Prompt einzubetten |
\] | Ende einer Folge nicht druckbarer Zeichen |
Sofern die standardmäßig gesetzte shopt-Option promptvars aktiv ist, unterliegt der Prompt-String nach der Dekodierung der o.g. Spezialzeichen der Parameter-Expandierung, der Kommando-Substitution, der arithmetischen Expandierung sowie der Entfernung der Quotierungs-Zeichen.
Die Kommando- und die History-Nummer eines Kommandos können differieren. Die History-Nummer gibt die Position des Kommandos in der History-Liste an, welche auch Kommandos enthalten kann, die aus einer History-Datei eingelesen wurden und somit aus einer anderen Shell-Sitzung stammen. Dagegen bezieht sich die Kommando-Nummer immer auf den aktuellen Shell-Prozess und wird für jedes neue Kommando um eins erhöht.
Kommando- und die History-Nummer unterscheiden sich auch dann, wenn ein ausgeführtes Kommando auf Grund einer History-Option (z.B. ignoreboth in HISTCONTROL) nicht in die History aufgenommen wird oder nachdem die History vom Nutzer partiell oder komplett gelöscht wurde, weil dadurch die aktuelle History-Nummer verringert werden kann.
Der folgende Auszug aus ~/.bashrc zeigt ein Beispiel für eine Anpassung des primären Prompts (PS1):
if (($UID)) then # Prompt-Zeichen für Nicht-Root-Nutzer ist > pschar='$>' else # Prompt-Zeichen für root ist # pschar='\\$#' # Anmerkung: $pschar, also die Zeichenfolge # \\$# # wird unten in den Wert von PS1 übernommen. Dabei wird \\ von der # Prompt-String-Dekodierung als Backslash gewertet. Nach der # Dekodierung steht also die Zeichenfolge # \$# # in PS1. Diese wird wegen der gesetzten Option promptvars zu # $# # expandiert. Würde der Backslash fehlen, würde $# durch die Anzahl der # Positionsparameter ersetzt. fi if [[ $TERM == xterm* ]] then # Hinweis: Alle Zeichen, die bei ihrer Ausgabe keinen Platz auf dem # Bildschirm einnehmen, sind in \[ und \] einzuschließen, weil sonst die # Readline-Bibliothek nicht ordentlich arbeiten kann. PS1="\[\e]2;\h:\w\a\e[7m\]\u@\h\[\e[0m\] \! $pschar " else PS1="\[\e[7m\]\u@\h\[\e[0m\] \W \! $pschar " fi
Erläuterung:
Für PS1 werden zwei Einstellungen vorgenommen: die erste für Shells, die in einem XTerm-artigen Terminal-Emulator laufen, die zweite für alle anderen Shells (also z.B. auf der Linux-Text-Konsole).
Beschreibung der Elemente von PS1 für Shells im XTerm-artigen Terminal-Emulator:
\[ | Beginn einer Folge nichtdruckbarer Zeichen |
\e]2; | Zeichenfolge mit 3 Zeichen: ESC (Escape) 2 ; Bedeutung: Beginn einer Escape-Sequenz zum Setzen der Titelzeile im XTerm-Fenster |
\h:\w | Host-Name in Kurzform gefolgt von einem Doppelpunkt gefolgt vom kompletten absoluten Pfad zum aktuellen Verzeichnis |
\a | Ctrl-G (Bell) zum Beenden der Escape-Sequenz, die die XTerm-Titelzeile setzt |
\e[7m | Zeichenfolge mit 3 Zeichen: ESC 7 m Bedeutung: inverse Schrift einschalten (Wechsel von Vorder- und Hintergrund-Farbe) |
\] | Ende einer Folge nichtdruckbarer Zeichen |
\u@\h | Nutzer-Name gefolgt von @ gefolgt vom Host-Namen in Kurzform |
\[\e[0m\] | Escape-Sequenz ESC 0 m zum Beenden der inversen Schrift durch Zurückschaltung auf die normale Schrift (wird wieder als Folge nichtdruckbarer Zeichen angegeben) |
\! $pschar | Leerzeichen gefolgt von der History-Nummer des aktuellen Kommandos gefolgt vom oben gewählten Prompt-Zeichen (# für root, $ sonst) gefolgt von einem weiteren Leerzeichen |
Beschreibung der Elemente von PS1 für Shells, die nicht im XTerm laufen:
\[\e[7m\] | inverse Schrift einschalten (s. oben) |
\u@\h | s. oben |
\[\e[0m\] | Zurückschaltung auf die normale Schrift (s. oben) |
\W | letztes Element des absolutes Pfades zum aktuellen Verzeichnis, wobei das Home-Verzeichnis durch ~ (Tilde) symbolisiert wird |
\! $pschar | s. oben |
Die folgende Liste nennt einige der Neuerungen in Bash 3.0 gegenüber der Bash 2:
Bash 3 bietet einen besseren Debugger-Support durch die Optionen --debugger und extdebug. Wer allerdings wirklich mit dem Bash-Debugger arbeiten möchte, sollte die Quellen der Bash geeignet patchen, um den Bash-Debugger richtig zu integrieren.
Die Shell-Variable HISTCONTROL unterstützt die neue Option erasedups, die weiter oben schon erläutert wurde.
Die Brace-Expandierung unterstützt zusätzlich Sequenz-Ausdrücke der Form x..y zur Erzeugung von Sequenzen von Zahlen und Zeichen:
echo {1..5} # expandiert zu 1 2 3 4 5 echo {a..f} # expandiert zu a b c d e f
Für History-Einträge werden Zeitstempel gespeichert. Deren Ausgabe kann man über die Variable HISTTIMEFORMAT (siehe Beispiel weiter oben) formatieren.
Tests der Form [[ ... ]] beherrschen nun den Umgang mit erweiterten regulären Ausdrücken gemäß POSIX 1003.2 (vergleichbar den Mustern beim Kommando egrep):
Dafür existiert der neue Operator =~, dessen rechter Operand als regulärer Ausdruck interpretiert wird.
Wenn der reguläre Ausdruck syntaktisch falsch ist, liefert [[ ... ]] den Exit-Status 2. Wenn die shopt-Option nocaseglob gesetzt ist, dann erfolgt der Muster-Vergleich ohne Unterscheidung von Groß- und Kleinbuchstaben (also case-insensitive).
Gefundene Teil-Strings, die den geklammerten Unterausdrücken des Musters entsprechen, werden im Feld BASH_REMATCH gespeichert. Feld-Element n korrespondiert dabei mit dem Unterausdruck n. Das Element mit Index 0 entspricht dem Teil des Strings, der durch das komplette Muster gefunden wurde.
Beispiel:
# bis Bash 3.1 waren reguläre Ausdrücke hinter =~ als Strings zu notieren; # nach 3.1 muss man die Option compat31 setzen, wenn weiterhin Strings # genutzt werden sollen: # # shopt -s compat31 # datum=29.3.2005 if [[ $datum =~ ^([0-9]{1,2})\.([0-9]{1,2})\.([0-9]{4})$ ]] then echo "Tag : ${BASH_REMATCH[1]}" echo "Monat: ${BASH_REMATCH[2]}" echo "Jahr : ${BASH_REMATCH[3]}" echo "Datum: ${BASH_REMATCH[0]}" else echo 'Syntaxfehler!' fi
Die neue Option pipefail bewirkt folgendes: Wenn ein beliebiges Kommando der Pipe fehlschlägt, wird dessen Exit-Status als Exit-Status der gesamten Pipe gemeldet. Exit-Status 0 besagt dann, dass alle Kommandos der Pipe erfolgreich waren.
Die neue Option -E bzw. errtrace bewirkt die Vererbung von ERR-Traps an Shell-Funktionen, Kommando-Substitutionen und Kommandos in Sub-Shells.
Die neue Option -T bzw. functrace bewirkt die Vererbung von DEBUG-Traps an Shell-Funktionen, Kommando-Substitutionen und Kommandos in Sub-Shells.
Die neue Variable BASH_EXECUTION_STRING enthält das Kommando-Argument der Bash-Option -c.
Das neue Builtin-Kommando caller steht zur Verfügung:
Syntax:
caller [Nummer]
Bei Aufruf ohne Argument erfolgt die Anzeige von Zeilennummer und Name der Quelltext-Datei, von wo aus die aktuelle Sub-Routine aufgerufen wurde.
Bei Aufruf mit nichtnegativer Ganzzahl Nummer als Argument erfolgt die Anzeige von Zeilennummer, Name der Sub-Routine sowie Name des Quelltext-Files an Position Nummer des aktuellen Aufruf-Stacks (also Anzeige von Stack-Frame n), wobei Index 0 den aktuellen Stack-Frame benennt.
Beispiel:
Quelltexte dreier Skripten: a.sh | b.sh | c.sh ------------------------------------------------ 1 #!/bin/sh | function b() | function c() 2 | { | { 3 function a() | c | caller 4 { | } | caller 0 5 b | | caller 1 6 } | | caller 2 7 | | caller 3 8 source b.sh | | } 9 source c.sh | | 10 | | 11 a | | Ausgabe: 3 b.sh 3 b b.sh 5 a a.sh 11 main a.sh
Die neuen Shell-Variablen BASH_LINENO, BASH_SOURCE und FUNCNAME stehen zur Verfügung:
Wenn keine Funktion aktiv ist, ist FUNCNAME leer. Wenn eine Funktion aktiv ist, hat das letzte Element von FUNCNAME immer den Wert main.
Beispiel:
BASH_LINENO=([0]="8" [1]="12" [2]="17" [3]="0") BASH_SOURCE=([0]="c.sh" [1]="b.sh" [2]="a.sh" [3]="a.sh") FUNCNAME=([0]="c" [1]="b" [2]="a" [3]="main")
Daraus kann man ablesen:
Die neue Variable BASH_SUBSHELL wird ausgehend von 0 in jeder Sub-Shell um 1 erhöht.
Über die neuen Konstrukte der Form ${!array[@]} oder ${!array[*]} kann man die Liste der Array-Indexe des jeweils benannten Feldes ermitteln.
Beispiel:
array=(a b c)
echo ${!array[@]} # Ausgabe: 0 1 2
Unter http://bashdb.sourceforge.net ist der Bash-Debugger von Rocky Bernstein zu finden, der von seiner Handhabung her an den GDB erinnert.
Nachfolgend sind verschiedene Tips aufgeführt, um sichere Shell-Skripten zu schreiben, die also (möglichst) immer das tun, was deren Autor beabsichtigt hat und einem Angreifer keine Chance bieten, die Funktionalität des Skripts zu ändern, um ggf. unautorisierte Manipulationen am System vorzunehmen.
Die Liste erhebt keinen Anspruch auf Vollständigkeit.
Der Suchpfad PATH sollte das aktuelle Verzeichnis (also .) nicht enthalten, um zu vermeiden, dass man aus Versehen ein Schad-Programm aktiviert, das ein Angreifer an einer Stelle hinterlegt hat, an der er regulär Schreibrecht hat (z.B. in seinem Home-Verzeichnis).
Alle Programme, die über den Suchpfad PATH erreicht werden, müssen durch eine sorgfältige Rechte-Vergabe (standardmäßig via chmod und chown) gegen unberechtigte Manipulation geschützt sein.
Argumente, die der Aufrufer eines Skripts beeinflussen kann, sind bei Bedarf auf Gültigkeit zu prüfen bzw. generell geeignet hart durch das Skript zu setzen.
Dazu zählen Positionsparameter, Umgebungsvariablen, vom Nutzer generierte Dateinamen und Daten, die mit dem Kommando read eingelesen werden. Potentiell kritische Umgebungsvariablen wie PATH und IFS sollten beim Skript-Start hart gesetzt werden.
Empfehlung des Bash-Maintainers Chet Ramey für einen Shell-Skript-Prolog:
# Variable IFS setzen IFS=$' \t\n' # sicherstellen, dass unalias keine Funktion, sondern ein reguläres # Builtin-Kommando ist; # unset ist ein spezielles Builtin-Kommando, das vor Funktionen gefunden wird unset -f unalias # alle Alias-Definition löschen; # durch den Backslash vor unalias wird sichergestellt, dass unalias keiner # Alias-Expandierung unterliegt \unalias -a # sicherstellen, dass command keine Funktion, sondern ein reguläres # Builtin-Kommando ist unset -f command # PATH geeignet einstellen, auch auf Systemen, bei denen kein Kommando getconf # zur Verfügung steht, über das man die System-Konfiguration auslesen kann SYSPATH="$(command -p getconf PATH 2>/dev/null)" if [[ -z $SYSPATH ]] then # getconf schlug fehl; wir setzen selbst einen sinnvollen Standard SYSPATH="/usr/bin:/bin" fi PATH="$SYSPATH:$PATH"
Des weiteren empfiehlt es sich, die Dateierzeugungsmaske mit dem Kommando umask geeignet zu setzen, um die Datei-Zugriffsrechte bei der Erstellung neuer Dateien gezielt zu beeinflussen.
Beispiel:
umask 077 # Gruppe und Welt bekommen keine Rechte umask 022 # Gruppe und Welt bekommen kein Schreib-Recht
Die Fehler-Codes aller aufgerufenen Kommandos sind auszuwerten.
Beispiele:
# Skript mit Exit-Code 1 beenden, wenn kommando fehlschlug kommando || exit 1 # analog, aber zusätzlich mit expliziter Fehlermeldung if ! kommando then echo 'kommando schlug fehl' exit 1 fi # kommando2 nur ausführen, wenn kommando1 erfolgreich war kommando1 && kommando2
Es sei hier nochmal auf die Option e bzw. errexit hingewiesen, die bewirkt, dass die Bash nach fehlerhaft (also mit einem Exit-Status ungleich 0) beendeten Kommandos im Standardfall automatisch beendet wird. Dies passiert allerdings nicht, wenn das fehlgeschlagene Kommando hinter while oder until folgt, im Rahmen von if oder einer UND- bzw. ODER-Liste (&& und ||) getestet oder sein Exit-Status mittels ! invertiert wird.
Beim Skript-Start sollte bei Bedarf ein geeignetes aktuelles Verzeichnis eingestellt werden.
Beispiel:
cd path || exit 1
Unabhängig vom PATH kann es sinnvoll sein, externe Kommandos über deren vollständigen Pfadnamen zu rufen, um sicher zu sein, welche Version man nutzt.
Durch Log-Nachrichten können und sollten wichtige Aktionen eines Skripts protokolliert werden.
Das Kommando logger erzeugt syslog-Einträge (automatisch mit Zeitstempel), z.B.:
logger -p daemon.info "skript gestartet von $USER $(id -un)"
Ggf. sind auch eigene Log-Dateien sinnvoll (Zeitstempel müssen hier vom Skript selbst eingebracht werden), z.B.:
function logger() { printf "%s\n" "$*" >> /var/adm/my_log_file } logger "$(date) skript gestartet von $USER $(id -un)"
Ein bequemes Logging aller Ausgaben des Skripts kann durch das Kommando exec erreicht werden, indem man Standard-Ausgabe und -Fehlerstrom geeignet umlenkt:
LOG_FILE=/var/adm/my_log_file # Log-Datei neu anlegen exec > $LOG_FILE 2>&1 # existierende Log-Datei fortschreiben exec >> $LOG_FILE 2>&1
Zur Vermeidung unvorhersehbarer Seiteneffekte sollten Nutzer-Daten (also alle vom Skript verwendeten Daten, die der Nutzer beeinflussen kann) bei deren Weiterverwendung geeignet durch Quotierung geschützt und niemals ungeprüft durch das Kommando eval einer erneuten Auswertung zugeführt werden.
Quotierungs-Beispiele:
"$1" "$*"
Hinweis: Auch Dateinamen können Shell-Metazeichen (wie Leerzeichen, Semikolon, Backquote) enthalten, deren Sonderbedeutung bei der Verwendung von eval gefährlich werden kann.
Beispiel für eine Attacke über Dateinamen:
Skript eines Administrators, der sorglos alle Dateien in /tmp löschen will, deren Namen auf bak enden:
# ins Verzeichnis /tmp wechseln cd /tmp # das auf die Dateien anzuwendende Kommando in der Variablen # cmd ablegen, um das Skript flexibler ändern zu können cmd=rm # in einer Schleife alle bak-Dateien bearbeiten for file in *bak do eval $cmd "$file" done
Aktionen des Angreifers:
# ins Verzeichnis /tmp wechseln cd /tmp # leer Datei kopie erzeugen, die dem Angreifer gehört touch kopie # symbolischen Link auf /etc/shadow legen ln -s /etc/shadow # Datei mit dem Namen -f ; rm datei ; cat shadow >> kopie #bak anlegen touch '; rm datei ; cat shadow >| kopie #bak'
Im Namen der zuletzt erzeugten Datei sind zwei Shell-Kommandos enthalten. Das Suffix #bak ist aus Sicht der Kommandos ein Kommentar. Die Endung bak bewirkt aber, dass diese Datei vom obigen Administrator-Skript erfasst wird, da sie ja auf bak endet.
Durch das eval wird die Ausführung der beiden vom Angreifer kodierten Kommandos bewirkt. Dies wird auch nicht dadurch verhindert, dass $file durch Doppel-Apostrophe quotiert wurde. Die Administrator-Shell führt real folgendes Kommando aus:
rm -f ; rm -f datei ; cat shadow >> kopie
Das erste Teilkommnando tut nichts, da keine zu löschende Datei angegeben wurde. Durch die Option -f erfolgt aber auch keine Fehlermeldung.
Das zweite Kommando löscht die Datei datei, sofern sie existiert. Diese kann auch einem beliebigen Nutzer gehören. Der Angreifer kann also das Administrator-Skript zur Löschung fremder Dateien missbrauchen.
Das dritte Kommando hängt den Inhalt der sonst nur für den Administrator lesbaren Datei /etc/shadow an die Datei /tmp/kopie an. Da letztere dem Angreifer gehört, kann er sie auch auslesen.
Im obigen Beispiel ist die Verwendung von eval unnötig. Man kann leicht darauf verzichten und so das Sicherheits-Problem vermeiden. Ohne eval wird jeder gefundene Dateiname unverändert an das Kommando rm übergeben. Damit werden die gefährlichen Shell-Metazeichen von der Shell nicht als solche interpretiert.
Hätte man auf die Quotierung verzichtet, also innerhalb der for-Schleife das Kommando
$cmd $file
notiert, so wäre der Dateiname in einzelne Wörter aufgesplittet worden. Diese einzelnen Wörter wären dann als vermeintliche Dateinamen an das Kommando rm übergeben worden. Die Shell würde de facto folgendes Kommando ausführen:
rm ';' rm datei ';' cat shadow '>>' kopie '#bak'
Sofern eval wirklich in einem Skript angewendet werden muss, sind alle "gefährlichen" Meta-Zeichen der Shell geeignet zu quotieren oder vorher zu eliminieren.
Skript-Entwickler müssen Wettlaufsituationen (race conditions) bedenken, also Situationen, bei denen das Resultat einer Folge von Operationen von deren zeitlicher Abfolge abhängt. Konkret geht es darum, dass zwischen mehreren Kommandos eines Shell-Skripts, die eine logische Einheit bilden und daher eigentlich atomar (also unteilbar) sein müssten, andere Prozesse zur Ausführung gelangen können, die gezielt oder zufällig die logische Atomarität der Shell-Kommando-Folge zerstören und so ein Fehlverhalten des Skripts bewirken. Angreifer nutzen Wettlaufsituationen, um die Funktionalität eines Skripts gezielt in ihrem Sinne zu ändern.
Beispiel:
Ein Skript ermittelt in Schritt 1 die Liste aller Dateien und Verzeichnisse eines Teilbaums, die einem bestimmten Kriterium entsprechen und daher nachfolgend bearbeitet werden sollen. Anschließend durchläuft es in Schritt 2 diese Liste und bearbeitet jedes Element ohne nochmalige Kontrolle.
Wenn der Angreifer nach Schritt 1 die Chance hat, ein Element der Liste im Dateisystem durch einen symbolischen Link auf ein anderes Objekt (Datei oder Verzeichnis) im Dateisystem zu ersetzen (was z.B. in allgemein schreibbaren Verzeichnissen wie /tmp relativ leicht möglich ist), kann er das Skript ggf. veranlassen, das Ziel des Links (das andere Objekt) zu manipulieren. Diese Manipulation hat das Skript nicht beabsichtigt, sondern letztlich der Angreifer vorgegeben.
Wenn das Skript Administrator-Rechte besitzt, kann der Angreifer über den obigen Weg die Manipulation von Dateien oder Verzeichnissen veranlassen, auf die er selbst keinen Zugriff hat.
Wettlaufsituationen sind mit Shell-Mitteln allein meist relativ schwer oder gar nicht vernünftig zu behandeln, da es auf dieser Ebene schwierig ist, atomare Operationen zu realisieren. Ggf. muss man hierfür auf geeignete Hilfs-Programme zurückgreifen, die dazu bessere Möglichkeiten bieten, z.B. Werkzeuge, die in der Sprache C implementiert wurden und geeignete Kernrufe (z.B. fchdir, fstat, fchown, fchmod) verwenden, da letztlich nur der Kern eine Atomarität sichern kann.
Die weiter unten folgende Empfehlung, z.B. mit test -L Links zu erkennen, verhindert allein eine Wettlaufsituation noch nicht zwingend, da der Angreifer ggf. wieder die Möglichkeit hat, zwischen dem Test-Kommando und dem folgenden Manipulations-Kommando Änderungen am Dateisystem vorzunehmen, die das Skript nicht bemerken kann.
Abhilfe könnte evtl. schaffen, dass das Skript den zu testenden Eintrag vor dem Test in ein Verzeichnis verschiebt, in dem nur das Skript Schreibrechte hat. Dann kann es den Test und evtl. Modifikationen ungestört vornehmen. Das Verzeichnis, das man bei Bedarf mit mktemp -d temporär anlegen kann, sollte dabei in derselben Partition wie das zu testende Objekt liegen, weil dann die Verschiebung sehr schnell geht und keine Kopier-Aktionen nach sich zieht.
Ob ein solches Verfahren sinnvoll anwendbar ist, muss man für den konkreten Fall entscheiden. Zu bedenken ist, dass das verschobene Objekt eine gewisse Zeit nicht unter seinem eigtl. Namen zu finden ist, was anderen Programmen Probleme bereiten könnte. Außerdem ergibt sich ggf. die Aufgabe, das modifizierte Objekt an seine ursprüngliche Stelle zurückzuschieben, was Schwierigkeiten verursachen kann, wenn der Angreifer zwischenzeitlich dort selbst ein Objekt angelegt hat.
Es ist darauf zu achten, dass die maximale Länge der Kommandozeile nicht überschritten wird.
Beispiel:
# alle .tex-Dateien des aktuellen Verzeichnisses bearbeiten for file in *.tex do kommando "$file" done # bei sehr vielen Dateien läuft bei der obigen Realisierung # die Kommandozeile über; besser ist daher folgende Variante: ls | grep '\.tex$' | while read -r file do kommando "$file" done # ggf. hilft auch xargs find . -name '*.tex' -print0 | xargs -0 kommando
In Skripten sollte man die Verwendung fester oder vom Angreifer leicht ermittelbarer Dateinamen vermeiden, sofern ein Angreifer darüber das Skript beeinflussen kann.
Speziell temporäre Dateien und Verzeichnisse sollten über das Kommando mktemp angelegt werden:
# temporäre Log-Datei anlegen; falls unmöglich, dann /dev/null als Log-Datei verwenden LOG_FILE=$(mktemp /tmp/logf.XXXXXXXX 2>/dev/null) || LOG_FILE=/dev/null # analog, aber Skript beenden, falls Log-Datei nicht anlegbar ist LOG_FILE=$(mktemp /tmp/logf.XXXXXXXX 2>/dev/null) || exit 1 # temporäres Verzeichnis anlegen TMP_DIR=$(mktemp -d /tmp/tmp_dir.XXXXXXXX 2>/dev/null) || exit 1
Die Ressourcen-Limits (z.B. die maximale Anzahl und Größe von Dateien inkl. von Core Dumps) sollten geeignet eingestellt werden. Dies geschieht über das Builtin-Kommando ulimit bei bash und zsh bzw. limit bei tcsh.
Die aktuellen Limit-Einstellungen können über das Kommando
ulimit -a
auf die Standard-Ausgabe ausgegeben werden. Auch die möglichen Optionen zur Veränderung der Einstellungen werden dabei aufgelistet.
Symbolischen Links sollte man nicht blind folgen (s. dazu auch das Beispiel zu Wettlaufsituationen). Bei Bedarf sollte man testen, ob es sich um einen symbolischen Link handelt. Dazu kann man die Optionen -L und -h des Kommandos test verwenden, die funktionell äquivalent sind:
test -L file test -h file
Wie oben schon erwähnt, sind dabei allerdings wieder Wettlaufsituationen zu beachten. Nach der Ausführung des Kommandos test kann der Angreifer die Chance haben, den gerade getesteten Dateisystem-Eintrag zu manipulieren.
Shell-Skripten sollten möglichst kein gesetztes Setuid- oder Setgid-Bit nutzen. Besser ist es, einen Mechanismus wie sudo zu verwenden, um Nutzern in kontrollierter Weise die Ausführung von Skripten mit höheren Privilegien zu gestatten.
Weiter oben finden sich Hinweise zu Interpreter-Dateien sowie zur Setuid-/Setgid-Problematik unter Linux und zum privilegierten Modus der Bash.
Im Verzeichnis SCRIPTS liegen verschiedene Beispiel-Skripten bzw. -Programme. Die meisten von ihnen sind Bash-Skripte, daneben gibt es aber auch Vertreter, die in den Sprachen AWK, Python und C implementiert wurden. Alle Dateien (mit Ausnahme des aus dem C-Quelltext generierten Maschinen-Codes) enthalten Kommentare, die den Zweck des Skripts/Programms sowie seine Funktionalität erläutern. Auf einige Dateien wird im obigen Text verwiesen, andere stehen zusätzlich hier bereit.