Vorlesung "UNIX"

von Prof. Jürgen Plate

2 Einführung in die Shell

Die Shell ist der Kommandointerpreter von UNIX. Es haben sich drei Typen entwickelt:

Aus diesen sind weitere Shells entwickelt worden, z. B. die Bourne again shell, bash, die ähnliche Funktionen hat wie die ksh oder die tcsh als Abkömmling der csh.

2.1 Aufgaben der Shell

Es gibt weiterhin die Möglichkeit, Programme "im Hintergrund" zu starten und am Bildschirm weiterzuarbeiten, während das Programm läuft. Sobald die Shell bereit ist, Kommandoeingaben anzunehmen, meldet sie sich mit einem Bereitschaftszeichen (Prompt). Im einfachsten Fall ist dies ein "$"-Zeichen als normales Prompt, dem "#" als Prompt für Superuser und dem ">"-Prompt, wenn noch weitere Eingaben erwartet werden. Wie vieles in der Shell, kann der Prompt beliebig modifiziert werden.

Anmerkungen:

  1. In diesem Kapitel geht es um grundlegende Eingenschaften der Shell, später wird das Thema noch vertieft.
  2. Die Shell ist ein ganz normales Programm, das von der Standardeingabe (Tastatur) liest und auf die Standardausgabe (Bildschirm) ausgibt. Wenn das Dateiende erreicht wird (End Of File, EOF), z. B. durch Eingabe von CTRL-D, terminiert sie. Ist es die Login-Shell, erfolgt ein Logoff des Benutzers.
  3. Die Shell kann wie ein Programm als Subshell aufgerufen werden (Schachtelung). Dies wird beispielsweise benötigt, wenn man Shell-Programme (shell skripts) testen will.
  4. Suchpfade für Kommandos: Es gibt zwei Möglichkeiten, den Pfad für ein Kommando festzulegen:
    1. Direkt als absoluter oder relativer Pfadname
    2. Indirekt über den Pfad, der in der Shell-Variablen PATH gespeichert ist.

Kommandobeschreibung

In der Literatur (auch beim Kommando "man") werden die Kommandos in Kurzform beschrieben. Dabei werden Werte, die optional sind, in eckige Klammern gesetzt. Die (meist einbuchstabigen) Optionen werden als Kette aufgelistet, z.B. [-aeGhlz]. Das bedeutet nichts anderes, als daß eine beliebige Kombination dieser Optionen möglich ist (ob sie sinnvoll ist, wird hier nicht berücksichtigt).

UNIX-Benutzer sind "mündig"! Was heißt das? Wenn Sie ein Kommando eingeben, das die gesamte Platte löscht, fragt das Programm nicht noch einmal nach, ob Sie das auch wirklich wollen, sondern löscht die Platte sofort.

Beim Testen von Shell-Programmen hilft das Kommando

echo [Argumente]

Dieses Kommando gibt die Argumente auf dem Bildschirm aus. Für den Einsteiger ist das Kommando wichtig, weil er so die Kommandobearbeitung der Shell recht gut verfolgen und studieren kann (Einstreuen von echo-Kommandos in die Befehlsfolge).

Bearbeitung der Kommandozeile durch die Shell

Ein Kommando wird erst ausgeführt, wenn der Benutzer am Ende der Kommandozeile die RETURN-Taste drückt. Eine genauere Kenntniss des dann vonstatten gehenden Ablaufs erlaubt es, zu verstehen, warum etwas nicht so klappt, wie man es sich vorgestellt hat. Daher sollen diese Schritte hier kurz beschrieben werden. (Anmerkung: Einige der Kommandotrenner, `...`, Jokerzeichen und Variablen werden erst später behandelt.):
  1. Die Shell liest bis zum ersten Kommandotrenner (& && || ; > <) und stellt fest, ob Variablenzuweisungen erfolgen sollen oder die Ein-Ausgabe umgelenkt werden muß.
  2. Die Shell zelegt die Kommandozeile in einzelne Argumente. Sie trennt die einzelnen Argumente durch eines der Zeichen, die in der Shell-Variablen IFS (Internal Field Separator) stehen, normalerweise Leerzeichen, Tabs und Newline-Zeichen.
  3. Variablenreferenzen, die in der Kommandozeile stehen, werden durch ihre Werte ersetzt.
  4. Kommandos, die in `...` oder bei der bash in $(...) stehen, werden ausgeführt und durch ihre Ausgabe ersetzt.
  5. stdin, stdout und stderr werden auf ihre "Zieldateien" umgelenkt.
  6. Falls in der Kommandozeile noch Zuweisungen an Variablen stehen, werden diese ausgeführt.
  7. Die Shell sucht nach Jokerzeichen und und ersetzt diese durch passende Dateinamen.
  8. Die Shell führt das Kommando aus.

Login-Shell und Subshell

Eine Shell kann in zwei verschiedenen Modi laufen: als Login-Shell und als Subshell (Nicht-Login-Shell). Das Login-Programm setzt ein Flag, um der Shell mitzuteilen, dass sie eine Login-Shell ist. Eine Subshell wird auch durch den Aufruf eines Shell-Skripts, durch Aufruf des Shell-Kommandos (z.B. bash) oder durch den Aufruf einer Shell aus einem Anwendungsprogramm heraus) gestartet. Eine Subshell gibt keinen Prompt aus und hat normalerweise nur eine kurze Lebensdauer. Die Login-Shell richtet Dinge wie z. B. den Terminal-Typ, Suchpfad-Kommandos usw. ein. Die einzelnen Shells haben verschiedene Mechanismen, beim ersten Shell-Aufruf bestimmte Tätigkeiten auszuführen. Dazu werden sogenannte Shell-Setupdateien abgearbeitet, die Kommandos und Definitionen enthalten.

Shell Setup-Dateien (z . B. ".profile") führen typischerweise mindestens folgende Tätigkeiten aus:

Shell Setup-Dateien (Profile)

Hinweis: Bevor Sie eine Setup-Datei ändern, sollten Sie immer eine Sicherheitskopie von ihr erstellen, damit bei Problemen wieder auf das Original zurückgegangen werden kann und die Unterschiede zwischen der Originaldatei und der geänderten Version zum Finden von Fehlern untersucht werden können. Anstelle einer Backup-Datei kann man auch Zeilen auskommentieren anstatt sie zu löschen und neue Zeilen entsprechend markieren.

Je nachdem, welche Shell eingesetzt wird, gibt es unterschiedliche Setup-Dateien.

2.2 Ein-und Ausgabeumleitung

Die drei dem Terminal zugeordeneten Dateikanäle stdin, stdout und stderr können jederzeit auf Dateien umgeleitet werden. Den drei Standarddateien sind die Filehandles 0 (stdin), 1 (stdout) und 2 (stderr) zugeordnet.

Eingabeumleitung

Das Programm liest nun nicht mehr von der Tastatur (stdin), sondern aus einer Datei, bis das Dateiende erreicht ist.

Die Eingabeumleitung erfolgt durch das Zeichen "<", gefolgt von einem Dateinamen.

Kommando < Dateiname

Statt z. B. beim write-Kommando den Text direkt einzugeben, kann auch einen Datei an ein anderes Terminal gesendet werden:

write markus < Nachricht

Ausgabeumleitung

Die Ausgabe des Programms wird nicht auf dem Bildschirm (stdout) ausgegeben, sondern in einen Datei geschrieben. Die Ausgabeumleitung erfolgt durch das Zeichen ">", gefolgt von einem Dateinamen.

Falls die Datei noch nicht vorhaden war, wird sie automatisch angelegt. Falls die Datei schon vorhanden ist, wird sie überschrieben, d. h. es wird immer ab dem Dateianfang geschrieben.

Kommando > Dateiname

Fehlermeldungen (stderr) erscheinen nach wie vor auf dem Bildschirm. Beispiel: Ausgabe der Verzeichnisbelegung in einen Datei:

ls -l > info

Umlenkung der Fehlerausgabe (stderr)

Die Umleitung der Fehlerausgabe erfolgt genauso, wie die Ausgabeumleitung, jedoch wird hier die Zeichenfolge "2>" verwendet, da stderr die Handlenummer 2 hat.

Kommando 2> Fehlerdatei

(Die Umleitung der Standardausgabe ist nur die Kurzform von Kommando 1> Dateiname). Natürlich ist eine beliebige Kombination von Ein- und Ausgabeumleitung möglich, z. B.

Kommando < Eingabedatei > Ausgabedatei 2> Fehlerdatei

Anhängen von Infos an eine Datei

Es ist auch möglich, die Ausgabe des Programms an eine bereits vorhandene Datei anzuhängen. Dazu wird des ">" doppelt geschrieben.

Kommando >> Sammeldatei

Dazu ein paar Beispiele:

Dateiliste und aktive Benutzer in einen Datei schreiben:

ls -l > liste
who >> liste

Durch Umleitung von Ein- und Ausgabe läßt sich auch unterdrücken. Für die Ausgabe schreibt man

Kommando > /dev/null

oder für die Fehlerausgabe

Kommando 2> /dev/null.

Beides läßt sich auch kombinieren:

Kommando > Ergebnisdatei 2> /dev/null.

Will man ein Programm mit einem beliebigen Eingabedatenstrom versorgen, schreibt man

Kommando < /dev/zero.

Die Umleitung von stout und stderr in dieselbe Datei würde prinzipiell eine zweimalige Angabe der Datei (eventuell mit einem langen Pfad) erfordern. Für die Standarddateien werden in solchen Fällen spezielle Platzhalter verwendet:

&0Standardeingabe
&1Standardausgabe
&2Standard-Fehlerausgabe

Kommando > ausgabe 2>&1

Wenig bekannt ist der Bourne-Shell-Operator <>. Er öffnet eine Datei zum Lesen und Schreiben und verbindet sie mit der Standardeingabe. Fehlende Dateien legt er automatisch an, im Gegensatz zu > löscht er jedoch nicht den Inhalt bestehender Dateien. Gut eignet sich <> vor allem für den Zugriff auf Geräte, die eine bidirektionale Verbindung voraussetzen - etwa Terminals oder Modems.

Die Bourne-Shell kann noch mehr: Man kann einen beliebigen Kommunikationskanal umlenken, indem er dessen Kennzahl direkt vor das Größer oder Kleiner-Zeichen schreibt. Wie schon erwähnt, steht '0' für die Standardeingabe, 'l' und '2' stehen für die Standard- beziehungsweise Fehlerausgabe.

Allgemein gilt also:
Mit den Bourne-Shell-Operatoren <& und >& lassen sich Ein- und Ausgabekanäle miteinander verbinden. Vor dem Operator darf die Nummer des umgeleiteten Kanals stehen, dahinter muß die des Quell- beziehungsweise Zielkanals folgen. Die häufigste Konstruktion >datei 2>&1 leitet Standard- und Fehlerausgabe in dieselbe Datei um.

2.3 Pipes

Eine Pipe verbindet zwei Kommandos über einen temporären Puffer, d. h. die Ausgabe vom ersten Programm wird als Eingabe vom zweiten Programm verwendet. Alles, was das erste Programm in den Puffer schreibt, wird in der gleichen Reihenfolge vom zweiten Programm gelesen. Pufferung und Synchronisation werden vom Betriebssystem vorgenommen. Der Ablauf beider Prozesse kann verschränkt erfolgen. In einer Kommandofolge können mehrere Pipes vorkommen. Der Pipe-Mechanismus wird durch das Zeichen "|" (senkrechter Strich) aktiviert:

Kommando 1 | Kommando 2

Beispiel: Ausgabe der Dateien eines Verzeichnisses mit der Möglichkeit, zu blättern:

Natürlich können auch mehrere Kommandos hintereinander durch Pipes verbunden werden:

Kommando 1 | Kommando 2 | Kommando 3 | Kommando 4 | ...

Pipelines haben Vorrang vor anderen Formen der Ein- und Ausgabeumleitung. Bevor die Shell mit der Ausführung der Einzelbefehle und der dazugehörenden Umleitungen beginnt, baut sie die gesamte Pipeline zusammen: Sie erzeugt für jeden Abschnitt einen neuen Prozeß und verbindet die Standardausgabe jedes Prozesses mit der Standardeingabe des Nächsten. Will der Anwender die Fehlerausgabe eines Programms ebenfalls durch die Pipeline schicken, kann er in Bourne-Shell-Skripten

foo 2>&1 | bar
schreiben. Da die Shell die Pipeline-Verbindung zuerst herstellt, landen die Fehlermeldungen vom Programm foo auf der Standardeingabe von bar.

Kommandofolgen, die durch Pipes verbunden sind, werden auch als "Filter" bezeichnet. Einige nützliche Filter sind in jedem UNIX-System verfügbar. Zum Beispiel:

head [-n] [datei(en)]
Ausgabe der ersten n Zeilen aus den angegebenen Dateien. Voreinstellung ist 10 Zeilen. Wird keine Datei angegeben, liest head von der Standardeingabe.

tail [-/+n] [bc[f|r]] [datei]
Ausgabe der letzten n Zeilen einer Datei. Voreinstellung für n ist 10. Wird keine Datei angegeben, liest tail von der Standardeingabe.

Optionen
+nab der n. Zeile ausgeben
-ndie letzten n Zeilen ausgeben Wird hinter die Zahl n ein 'b' gesetzt (z. B. -15b), werden nicht n Zeilen, sondern n Blöcke ausgegeben. Wird hinter die Zahl n ein 'c' gesetzt (z. B. -200c), werden nicht n Zeilen, sondern n Zeichen (characters) ausgegeben.
-r Zeilen in umgekehrter Reihenfolge ausgeben (letzte zuerst). Geht nicht bei GNU-tail - stattdessen kann man das Programm toc verwenden.
-f tail am Dateiende nicht beenden, sondern auf weitere Zeilen warten. (Ende des Kommandos mit der CTRL-C-Taste). Damit kann man z. B. Logfiles beobachten, die ständig wachsen.

Mittels head und tail lassen sich beispielsweise auch ganz bestimmte Zeilen aus einer Datei extraihieren - wobei dieses Kommandos nicht die einzige Möglichkeit darstellen. Hier einige Beispiele für Kommandoverkettungen (die mit '#' beginnenden Zeilen sind Kommentarzeilen):

# 5. Zeile der Datei foo.txt
head -5 foo.txt | tail -1

# die drittletzte und vorletzte Zeile von foo.txt
tail -3 | head -2

tee [-i] [-a] [datei]
Pipe mit T-Stück: Kopiert von stdin nach stdout und schreibt die Daten gleichzeitig in die angegebene Datei.

Optionen
-iIgnorieren von Interrupts (Unterbrechungs-Taste)
-aAnhängen der Info an die angegebene Datei (Voreinstellung: Überschreiben der Datei)

wc [-lwc] [Datei(en)]
Dieses Kommando zählt Zeilen, Worte oder Zeichen in einer Datei. Wird kein Dateiname angegeben, liest wc von der Standardeingabe. Normalerweise zählt man damit in Skripten irgendwelche Ergebnisse. Optionen:

-l Zähle Zeilen
-wZähle Worte
-cZähle Zeichen

Weitere Filter sind more, less, tr, nl, .... Diese Kommandos werden in späteren Abschnitten behandelt.

Named Pipe

Eine weitere Möglichkeit zum Datentransfer zwischen mehreren Scripts, und das ogar auch zwischen unterschiedlichen Usern, bietet die Named Pipe (FIFO - First In First Out). Der Vorteil (manchmal auch Nachteil - je nach Anwendungsfall) ist hierbei, dass ein Prozess, der etwas in eine Pipe schreibt, so lange blockiert wird, bis auf der anderen Seite ein Prozess auch etwas daraus liest. Für beide Prozesse erscheint die Named Pipe wie eine Datei. Der Sende-Prozess leitet seine Ausgabe in die Pipe, der Empfänger-Prozess leitet seine Eingabe auf die Datei um. Soll eine bidirektionale Kommunikation stattfinden, werden zwei Named Pipes benötigt. Auch ist die Named Pipe nicht nur individuell nutzbar. Ein typischer Anwendungsfall wäre ein "Server", der Daten von beliebigen Clients entgegennimmt.
# erstmal Named Pipe erzeugen
mknod roehre p

# nun die Daten verarbeiten
tail -f roehre
Statt einer Ausgabe auf dem Bildschirm können die Daten auch anderweit verarbeitet werden.

2.4 Metazeichen zur Expansion von Dateinamen

Damit man beim Angeben von z. B. Dateinamen nicht alle Namen eintippen muß, sondern die Dateien auch alle oder nach bestimmten Kriterien auswählen kann, gibt es Metazeichen (Jokerzeichen, Wildcards). Im Gegensatz zu anderen Systemen (z. B. MS-DOS) werden diese von der Shell ersetzt. Dies ist eine ganz wichtige Tatsache, die zur Folge hat, daß nahezu jedes UNIX-Kommando als Dateiangabe immer eine (im Rahmen der BS-Parameter) beliebige Menge von Dateien als Parameter haben kann. Im Programm sind daher auch keine Systemaufrufe nötig, die auf die Verzeichnisinformation zugreifen; es wird lediglich eine Schleife benötigt, welche die einzelnen Dateien nacheinander bearbeitet. Metazeichen sind Zeichen mit erweiterter Bedeutung. Die Shell ersetzt die Metazeichen durch alle Dateinamen des aktuellen Verzeichnisses, die auf das Muster passen. Dabei können die Metazeichen beliebig oft an beliebiger Stelle im Dateinamen stehen (z. B.: *abc*def*). Es gibt folgende Metazeichen:

* Der Stern steht für eine beliebige Zeichenfolge - oder für überhaupt kein Zeichen. Dazu ein Beispiel:
"ab*" steht für alle Dateinamen, die mit "ab" anfangen, auch für "ab" selbst ("ab", "abc", "abcd", "abxyz", usw.).
? Das Fragezeichen steht für genau ein beliebiges Zeichen. Zum Beispiel:
"?bc" steht für alle Dateinamen mit 3 Zeichen, die auf "bc" enden ("abc", "bbc", "1bc", "vbc", "xbc", usw.), nicht jedoch für "bc".
[ ] Die eckige Klammer wird ersetzt durch eines der in der Klammer stehenden Zeichen. Auch ein Bereich ist möglich, z. B. [a-k] = [abcdefghijk]. Beispiel: "a[bcd]" wird ersetzt durch "ab", "ac" und "ad". Soll das Minuszeichen selbst in die Zeichenmenge aufgenommen werden, muß es an erster Stelle stehen (gleich nach der öffnenden Klammer).
[! ] Die eckige Klammer mit Ausrufezeichen wird ersetzt durch eines der nicht in der Klammer stehenden Zeichen, zum Beispiel: "[!abc]" wird ersetzt durch ein beliebiges Zeichen außer a, b oder c. Soll das Ausrufezeichen selbst in die Zeichenmenge aufgenommen werden, muß es an letzter Stelle stehen.
\ Der Backslash hebt den Ersetzungsmechanismus für das folgende Zeichen auf. Beispiel: "ab\?cd" wird zu "ab?cd" - das Fragezeichen wird übernommen. Wichtig: Bei der Umleitung von Ein- und Ausgabe werden Metazeichen in den Dateinamen hinter dem Umleitungszeichen nicht ersetzt.

Beispiele für die Anwendung:
ls -l a* listet alle Dateien, die mit "a" anfangen

ls test? listet alle Dateien die mit "test" anfangen und 5 Zeichen lang sind ("test1", "test2", "testa")

ls /dev/tty1[1-9] listet alle Terminalbezeichnungen mit einer 1 in der Zehnerstelle ("tty11", "tty12", ... , "tty19")

Lebenswichtig:
Der * ist ein gefährliches Zeichen, Tippfehler könne zum Fiasko führen, wenn aus Versehen ein Leerzeichen zuviel getippt wird.
rm a* löscht beispielsweise alle Dateien, die mit "a" anfangen.
rm a * löscht dagegen erst die Datei "a" und dann alle Dateien im Verzeichnis.

Anmerkungen:

2.5 String-Ersetzungen (Quoting)

Um bestimmte Sonderzeichen (z. B. *, ?, [ ], Leerzeichen, Punkt) zu übergeben, ohne daß sie von der Shell durch Dateinamen ersetzt werden, werden Anführungszeichen verwendet, die auch ineinander geschachtelt werden können. Dabei haben die drei verschiedenen Anführungszeichen Doublequote ("), Quote (') und Backquote (`) unterschiedliche Bedeutung:

"..." Keine Ersetzung der Metazeichen * ? [ ], jedoch Ersetzung von Shellvariablen (siehe unten) und Ersetzung durch die Ergebnisse von Kommandos (Backquote). Auch \ funktioniert weiterhin. Dazu ein Beispiel:
echo Der * wird hier durch alle Dateinamen ersetzt
echo "Der * wird hier nicht ersetzt"
'...' Das einfache Anführungszeichen unterdrückt jede Substitution. Zum Beispiel:
echo 'Weder * noch `pwd` werden ersetzt'
`...` Zwischen Backquote (Accent Grave) gesetzte Kommandos werden ausgeführt und das Ergebnis wird dann als Parameter übergeben (d. h. die Ausgabe des Kommandos landet als Parameter in der Kommandozeile). Dabei werden Zeilenwechsel zu Leerzeichen. Braucht dieses Kommando Parameter, tritt die normale Parameterersetzung in Kraft. Zum Beispiel:
echo "Aktuelles Verzeichnis: `pwd`"

Weil die verschiedenen Quotes manchmal schwer zu unterscheiden sind, wurde bei der bash eine weitere Möglichkeit eingeführt. Statt in Backquotes wird die Kommandofolge in $( ... ) eingeschlossen., z. B.:
echo "Aktuelles Verzeichnis: $(pwd)"

Weil die Backquotes relativ leicht zu übersehen sind, hat die bash von Linux eine alternative Schreibweise implementiert. Hier kann alternativ

$( ... )
verwendet werden.

2.6 Bash - Die Linux-Shell

Die Bash (Bourne Again SHell) ist vollständig kompatibel zu der originalen Bourne-Shell aber sie hat etliche Erweiterungen erfahren. Sie ist bei fast allen Linux-Systemen die Standard-Shell.

Beim Start liest die Bash eine Reihe von Konfigurationsdateien ein, interpretiert deren Inhalt und übernimmt bestimmte Einstellungen. Was dabei genau geschieht, hängt von mehreren Faktoren ab.

Ist die Bash als Login-Shell eingestellt, wird zunächst die Login-Konfigurationsdatei .bash_profile im Home-Verzeichnis des Anwenders gesucht. Ist sie vorhanden, führt die Bash die darin enthaltenen Befehle aus. Fehlt diese Datei, sucht die Bash im selben Verzeichnis nach einer Konfigurationsdatei mit dem Namen .bash_login. Fehlt auch diese, wird versucht, die systemweit gültige Konfigurationsdatei /etc/profile einzulesen und auszuwerten. Als letzte Möglichkeit versucht die Login-Bash, die Datei .profile im Home-Verzeichnis des Anwenders auszuführen.

Als interaktive (Sub-) Shell liest die Bash ihre Konfiguration aus der Datei .bashrc (im Home-Verzeichnis des Anwenders). Eine interaktive Bash verwendet für den Befehlszeileneditor zusätzlich die Einstellungen aus der Konfigurationsdatei .inputrc. Diese Datei wird zunächst im Home-Verzeichnis des Anwenders gesucht, ist sie dort nicht vorhanden, wird im Konfigurationsverzeichnis /etc/ nach der Datei inputrc gesucht.

Eine nicht interaktive (Sub-) Shell, wie sie zum Start von Skripten verwendet wird, liest keine Konfigurationsdateien ein, sofern dies nicht explizit gefordert wird.

Einige Versionen der Bash (z. B. die von SuSE Linux verwendete) suchen als interaktive Shell zunächst im Konfigurationsverzeichnis /etc/ nach systemweit gültigen Einstellungen in der Datei bash.bashrc. Diese ist aber normalerweise nicht vorhanden bzw. leer. Anschließend und unabhängig davon, ob diese Datei gefunden wurde, wird die persönliche Konfigurationsdatei .bashrc im Home-Verzeichnis des Anwenders eingelesen.

Damit bei einer Login-Shell auch Funktionen einer interaktiven Shell zur Verfügung stehen, wird am Ende der Login-Konfigurationsdatei oft auch die persönliche Konfigurationsdatei im Home-Verzeichnis des Anwenders eingelesen.

SuSE Linux löst die Konfiguration der Bash in folgender Weise: Die systemweite Konfiguration erfolgt in der Datei /etc/profile, an deren Ende weitere Konfigurationsdateien ausgeführt werden:

Auch beim Verlassen der Shell können von der Bash noch automatisch Befehle ausgeführt werden. Die Bash führt beim Eintreffen eines exit-Befehls die Datei .bash_logout im Home-Verzeichnis des Anwenders aus. Vorsicht: Nicht alle Versionen der Bash werten diese Datei korrekt aus.

Die Ausführung der Dateien läßt sich über zwei Kommandozeilen-Parameter steuern. Mit dem Parameter -noprofile veranlassen Sie, daß die Bash keine der oben genannten Startdateien ausführt, mit -norc erreichen Sie, daß die persönliche Konfigurationsdatei -/.bashrc ignoriert wird.

Der Prompt

Die Bash gibt einen Eingabeprompt aus, häufig in der Form Username:Pfad> . Der Prompt ist über die Umgebungsvariable PS1 konfigurierbar. Ein Prompt in der o. a. Form resultiert aus der Einstellung PS1=\u:\w\$ .
Um andere Einstellungen auszuprobieren, müssen Sie die Variable neu belegen. Innerhalb dieser neuen Einstellungen können Sie einige spezielle Codes verwenden:

Codes für die Konstruktion des Prompts
CodeWirkung
\a Das Bell-Zeichen, wie es durch die Tastenkombination [Ctrl][g] erzeugt wird
\d das aktuelle Datum im Format Thu Jan 18
\e das Escape-Zeichen
\H der gesamte (Host) -Name
\h der Rechner (Host) -Name bis zum ersten Punkt
\n ein Newline (LineFeed) -Zeichen
\r ein Return (Carriage Return) -Zeichen
\s der Programmname der Bash, also bash
\t die aktuelle Systemzeit im 24-Stundenformat HH:MM:SS
\T die aktuelle Systemzeit im 12-Stundenformat HH:MM:SS
\@ die aktuelle Systemzeit im 12-Stundenformat mit am/pm (01:39am)
\u der Username
\v die Version der ausgeführten Bash (2.03)
\V Das Release der Bash, bestehend aus der Versionsnummer und dem Patchlevel (2.03.1)
\w das aktuelle Arbeitsverzeichnis in ausführlicher Darstellung, beispielsweise ~/LinuxMagazin/bash/teil6
\W der letzte Teil des aktuellen Verzeichnisses, etwa teil6
\! die (History-) Nummer der aktuellen Befehlszeile
\# die Nummer der Befehlszeile in der aktuellen Bash-Sitzung
\$ mit diesem Schlüssen wird der Rootaccount gekennzeichnet. Wenn die effektive UID gleich Null ist, stellt die Bash das Hashmark dar, sonst ein Dollar-Zeichen
\NNN Jedes beliebige ASCII-Zeichen kann durch Eingabe des oktalen Codes nach einem Backslash erzeugt werden
\\ der Backslash selbst wird durch zwei Backslash-Zeichen erzeugt
\[ eine Folge von Steuerzeichen wird so eingeleitet
\] die Folge von Steuerzeichen wird so beendet

Edieren der Kommandozeile

Die Hauptaufgabe einer Shell ist die Entgegennahme und Ausführung von Kommandos. Zum Bearbeiten der aktuellen Kommandozeile stehen sowohl Emacs- als auch vi-kompatible Editiermodi zur Verfügung - voreingestellt ist der Emacs-Modus. Wenn Sie lieber im vi-Modus arbeiten, stellen Sie ihn durch den Befehl set -o vi ein, zurück in den Emacs-Modus geht's mit set -o emcas. Im Emacs-Modus gibt es unter anderem folgende Edierfunktionen:

Abhängig von der eingesetzten Terminalemulation können Sie Kombinationen mit der [Esc]-Taste oft auch mit der [Alt]-Taste nachgebilden. Statt also nacheinander [Esc] und [F] zu drücken, funktioniert meist auch die Kombination [Alt]+[F].

History-Mechanismus

In der Bash können Sie nicht nur die aktuelle Kommandozeile editieren, sie merkt sich auch alle einmal eingegebenen Befehle in einer Datei, der Kommandozeilen-History. Auch diese Datei befindet sich im Home-Directory des Benutzers und heißt .bash_history. Mit den Cursortasten auf/ab kann man in dieser Liste blättern. Darüber hinaus stehen folgende History-Befehle zur Verfügung:

Die Anzahl der Befehlszeilen wird mit der Variablen HISTSIZE eingestellt. Wächst die History darüber hinaus, verwirft die Bash die ältesten Zeilen.

Wichtige interne Kommandos

Mittels set einstellbare Optionen der Bash

Option Name Funktion
-a allexport neu definierte oder veränderte Variablen werden automatisch exportiert
-b notify bewirkt, dass Meldungen von Hintergrundjobs sofort ausgegeben werden (voreingestellt wartet die Bash bis zur Ausgabe des nächsten Prompts)
-B braceexpand Klammerexpandierungen erlauben (entspricht der Voreinstellung)
-C noclubber Setzen dieser Option verhindert, dass bestehende Dateien durch Ausgabeumleitungen (Redirections) zerstört werden
-e errexit In diesem Modus beendet sich die Shell immer dann automatisch, wenn ein Befehl einen Fehlercode erzeugte
-f noglob Deaktiviert die Komplettierungsfunktion für Dateinamen
-h hashall Deaktiviert das Speichern der Pfade bereits einmal ausgeführter externer Befehle; ein Abschalten bewirkt längere Ausführungszeiten bei Skripten
-H histexpand Erlaubt erweiterte Ersetzungen aus dem Historybuffer (voreingestellt: on)
-k keyword Zuweisungen werden in das Environment des Befehls übernommen
-m monitor Aktiviert die Job-Kontrollfunktionen (Voreinstellung: on bei interaktiven Shells)
-n noexec Verhindert die Ausführung von Befehlen; die Syntax wird aber überprüft und gegebenenfalls Fehlermeldungen erzeugt (deaktiviert bei interaktiven Shells)
-o Option setzt die im Argument übergebene Option, siehe das Beispiel oben
-p privileged Aktiviert den privilegierten Modus
-P physical Unterdrückt die Darstellung von symbolischen Links, statt dessen wird das physikalische Verzeichnis verwendet
-t onecmd Die Shell terminiert nach dem Ausführen des ersten Befehls
-u nounset Bewirkt, dass ungesetzt Variablen Fehlermeldungen erzeugen; ohne diese Option wird ihnen ein leerer Inhalt zugewiesen
-v verbose Befehlszeilen werden angezeigt, bevor sie ausgeführt werden
-x xtrace Alle Befehlszeilen werden mit expandierten Argumenten angezeigt, bevor sie ausgeführt werden

Mit der Einführung der Bash-Version 2.0 gibt es viele neue Konfigurations-Features. Sie werden durch den "erweiterten Konfigurationsbefehl" shopt gesetzt oder angezeigt. Die aktuellen Optionen sind über die Variable SHELLOPTS zugänglich. Der Befehl verfügt über folgende Optionen: -p (print) gibt eine Liste der aktuellen Einstellungen aus, -s (set) setzt die im Argument angegebene Option, -u (unset) löscht sie. Durch -q (quiet) wird die Ausgabe des Befehls unterdrückt, wie dies in Skripten praktisch sein kann. Durch -o beschränkt sich die Wirkung von shopt auf die durch set -o gesetzten Optionen. In der folgenden Tabelle sind nur die wichtigsten mittels shopt einstellbaren Bash-Optionen aufgeführt, eine vollständige Liste enthält die Manual-Page.

Option Funktion
cdable_vars bewirkt, dass die Bash Argumente des cd-Befehls als Variablen interpretiert, wenn es sich um keine Verzeichnisse handelt
cdspell einfache Schreibfehler (vertauschte oder fehlende Buchstaben) in Verzeichnisnamen werden durch diese Option automatisch korrigiert
checkhash die Bash sucht einen externen Befehl zunächst in der Hashtabelle, bevor er anhand des Suchpfades gefunden wird
checkwinsize wenn diese Option aktiviert ist, prüft die Bash nach jedem ausgeführten Befehl, ob sich die Terminal-Abmessungen geändert haben.
cmdhist zusammengehörige Befehlszeilen werden in der History in Form einer Zeile abgelegt, dadurch vereinfacht sich ihre Bearbeitung
dotglob durch das Setzen dieser Option werden auch die mit einem Punkt beginnenden Dateinamen beim automatischen Komplettieren berücksichtigt
execfail verhindert in Skripten, dass die Shell nach einem Fehler in einem exec-Befehl terminiert
histexpand bewirkt, dass Historydateien nicht mehr überschrieben, sondern an bestehende Dateien angehängt werden
lithist zusammen mit cmdhist bewirkt sie das Zusammenfassen mehrzeiliger Befehle
sourcepath der source-Befehl kann auf die PATH-Variable zugreifen, wenn diese Option gesetzt ist
expand_aliases erlaubt das Expandieren von Alias-Definitionen
nocaseglob bei der automatischen Dateinamenkomplettierung berücksichtigt die Bash Groß-/Kleinschreibung nicht, wenn diese Option aktiv ist
huponexit allen von einer interaktiven Shell ausgeführten Hintergrundjobs wird ein SIGHUP-Signal gesendet, wenn sie terminiert
restricted_shell die Bash wird im Restricted-Modus mit eingeschränkter Funktionalität betrieben

Arithmetik

Zusätzlich zu expr hat die Bash eingebaute Arithmetikfunktionen. Das Format der Erweiterung wird mit doppelten Klammern realisiert, wobei auch die Sonderfunktion der Metazeichen wie '*' abgeschaltet werden:
$((Ausdruck))
Diese Konstruktion wird dann folgendermaßen verwendet:
VAR=$((Ausdruck))
Zum Beispiel:
$ echo $(( 9 * 3 ))
27

$ X=$(( 2* 3 + 5 ))
$ echo $X
11
Als Operationen wird nahezu alles geboten, was auch in der Sprache C vorhanden ist:

Operator Bedeutung
wert++ Postinkrement der Variablen
wert-- Postdekrement der Variablen
++wert Präinkrement der Variablen
--wert Präiekrement der Variablen
+ - Plus und Minus
! ~ Logische und Bitweise Negation
* / % Mal, Teilen und Modulo
<< >> linkes und rechtes bitweises Verschieben
<= >= < > Vergleichsoperatoren
== != Gleich und Ungleich
& ^ | Bitweises UND, exklusives ODER und ODER
&& || Logisches UND und ODER
AUSDR1?AUSDR2:AUSDR3 Ergebnis: Wenn AUSDR1 dann AUSDR2 sonst AUSDR3

Es gibt nur einen Fallstrick: Wie bei C werden Zahlen mit führenden Nullen als Oktalzahlen (Basis 8) interpretiert. In folgenden Beispiel gibt es zum Glück eine Fehlermeldung:

$ echo $(( 09 * 3 ))
-bash: 09: Der Wert ist zu groß für die aktuelle Basis. (Fehlerverursachendes Zeichen ist \"09\").
Aber das muss nicht immer so sein, so ist 27 nicht gleich 027:
# dezimal
$ echo $(( 27 * 3 ))
81

# oktal
$ echo $(( 027 * 3 ))
69
Und denken Sie daran, dass es sich um Ganzzahl-Arithmetik mit begrenztem Wertebereich habdelt.

String-Manipulationen

Die Bash erlaubt es, aus einem String Teile herauszuschneiden (ähnlich dem Kommando cut). Die Syntax ist recht einfach:
${var:start:anzahl}

${var:start}
Damit schneiden Sie aus der Variablen var ab der Position start, anzahl Zeichen heraus. Fehlt die Angabe anzahl, wird von der Position start bis zum Ende kopiert. Zum Beispiel:
S=0123456789

echo ${S:3:4}
3456

echo ${S:3}
3456789

T=${S:3:5}
echo $T
34567

Arrays bei der Bash

Arrays ermöglichen es, eine geordnete Folge von Werten eines bestimmten Typs zu speichern und zu bearbeiten. Allerdings erlaubt die Bash nur eindimensionale Arrays mit einem ganzzahligen positiven Index. Das erste Element eines Arrays hat immer den Index 0. Oft werden Arrays in Schleifen verwendet. Hier muss man dann nicht auf eine Reihe von einzelnen Variablen zurückzugreifen.

Wenn Sie einer Array-Komponenten einen Wert zuweisen wollen, geben Sie den Feldindex an:

# Belegen der dritten(!) Arraykomponente mit dem String "zwo"
array[2]=zwo
Was geschieht in diesem Fall mit den Komponenten array[0] und array[1]? Bei der Bash-Programmierung dürfen Arrays Lücken enthalten (wie z. B. auch in Perl). Im Unterschied zu anderen Programmiersprachen müssen Sie in der Bash das Array nicht vor der Verwendung deklarieren. Sie sollten es jedoch trotzdem zu tun, denn einerseits kann man damit angeben, dass die Werte innerhalb eines Arrays als Integer gültig sein sollen (typeset –i array) und andererseits können Sie ein Array mit Schreibschutz versehen (typeset –r array).

Häufig will man beim Anlegen eines Arrays dieses mit Werten initialisieren. In der Bash geschieht dies durch Klammern der Werte:

array=(Merkur Venus Erde Mars Jupiter Saturn Uranus Neptun ...)
Bei der Zuweisung beginnt der Feldindex automatisch bei 0, also enthält array[0] den Wert "Merkur". Aber ist es auch möglich, eine Zuweisungsliste an einer anderen Position zu beginnen, imdem der Startindex angegeben wird:
array=([2]=Pluto Eris ...)
Diese Methode ist jedoch nicht geeignet, an ein Array neue Elemente anzuhängen. Ein existierendes Array wird immer komplett überschrieben. Die Anzahl der Elemente, die Sie einem Array übergeben können, ist bei der Bash beliebig. Als Trennzeichen zwischen den einzelnen Elementen dient das Leerzeichen.

Der Zugriff auf die einzelnen Komponenten eines Arrays erfolgt mit

${array[index]}
Für die Referenz auf den Arrayinhalt müssen geschweifte Klammern verwendet werden, da die eckigen Klammern ja schon Metazeichen der Bash sind (Bedingung analog dem test-Kommando) und sonst eine Expansion auf Shell-Ebene versucht würde.

Alle Elemente eines Arrays können Sie, ähnlich wie bei den Kommandozeilenparametern, folgendermaßen ausgeben lassen:

echo ${array[*]}
echo ${array[@]}
Der Unterschied ist der gleiche wie bei den Kommandozeilenparametern ($* und $@). Mit ${!arr[*]} lassen sich alle Indizes des Arrays auflisten. Dieses Feature ist erst später hinzugekommen. Diese Liste ist interessant, wenn das Array Lücken bei den Indizes aufweist. Im folgenden Beispiel werden Index und Inhalt aufgelistet:
for IND in ${!array[@]}
do 
  echo "$IND ${array[$IND]}"
done

Die Anzahl der belegten Elemente im Array erhalten Sie mit (auch diese Syntax ähnelt der Kommandozeile bzw. der Shell-Variablen $#):

echo ${#array[*]}
Wollen Sie feststellen, wie lang eine Array-Komponente ist, so gehen Sie genauso vor wie beim Ermitteln der Anzahl belegter Elemente im Array, nur dass Sie anstelle des Sternchens den entsprechenden Feldindex verwenden:
echo ${#array[1]}
Beispiele:
array=(Merkur Venus Erde Mars Jupiter Saturn Uranus Neptun)

echo ${array[4]}
Jupiter
echo ${#array[*]}
8
echo ${#array[2]}
4
Es lassen sich auch Teile eines Array selektieren. So liefern die folgenden Ausdrücke:
${array[@]}                # das gesamte Array (s. oben)
${array[@]:index:length}   # die Array-Komponenten von 'index' bis 'index+length-1' inklusive
${array[@]::length}        # die Array-Komponenten von '0' bis 'length-1' inklusive
${array[@]:index}          # die Array-Komponenten von 'index' bis zum Ende
Beispiele:
array=(Merkur Venus Erde Mars Jupiter Saturn Uranus Neptun)

echo "${array[@]:2}"
Erde Mars Jupiter Saturn Uranus Neptun

echo "${array[@]:1:3}"
Venus Erde Mars

echo "${array[@]::1}"
Merkur
Zum Löschen eines Arrays oder einer Komponente wird wie bei den Shell-Variablen vorgegangen. Das komplette Array löschen Sie mit:
unset array
Einzelne Elemente können Sie folgendermaßen löschen:
unset array[index]
Das funktioniert aber nicht immer, zum Beispiel:
INDEX=4

echo "${array[$INDEX]}"
Jupiter

unset ${array[$INDEX]}

echo "${#array[@]}"
8
# sollte aber nun '7' sein, weil Jupiter geloescht wurde

echo "${array[$INDEX]}"
Jupiter
# Ooops!
Der Fehler liegt darin, dass in der Bash ein Array als sogenannter Hash gespeichert wird ("sparse"). Also nicht darauf verlassen, dass eine Komponente nach dem unset tatsächlich weg ist.

Zum Kopieren eines kompletten Arrays können Sie entweder eine Schleife programmieren, in der jedes einzelne Element einem anderen Array zugewiesen wird. Es geht aber auch mit weniger Aufwand. Sie kombinieren das Auflisten aller Elemente des einen Arrays mit dem Zuweisen einer Liste mit Werten an ein zweites Array:

array_neu=( ${array[@]} )
Beispiel:
array=(Merkur Venus Erde Mars Jupiter Saturn Uranus Neptun)

echo ${array[@]}
Merkur Venus Erde Mars Jupiter Saturn Uranus Neptun

yarra=( ${array[@]} )

echo ${yarra[@]}
Merkur Venus Erde Mars Jupiter Saturn Uranus Neptun

2.7 Schlußbemerkung

Es gibt zwei Möglichkeiten, einem UNIX-Kommando beim Aufruf Informationen mitzugeben: Für die letztere Möglichkeit gibt es das Kommando cat Datei (concatenate): Lesen der angegebenen Datei und Kopieren auf die Standardausgabe. Mit cat Datei | Kommando bekommt man den Dateininhalt in die Standardeingabe des Kommandos.

Auch für die Ausgabe gibt es zwei Möglichkeiten:

Wichtig werden diese Erkenntnisse erst bei Erstellen von Shell-Skripts, wo von allen geschilderten Möglichkeiten Gebrauch gemacht wird.

Zum Inhaltsverzeichnis Zum nächsten Abschnitt
Copyright © Hochschule München, FK 04, Prof. Jürgen Plate