Zum Inhalt springen

Arbeitsweise der Shell

Die Shell ist kein Teil des Kernels und auch nicht «das Terminal». Sie ist ein ganz normales Programm, das eine Zeile liest, sie interpretiert und dann andere Programme startet. Das Terminal ist nur das Fenster, in dem die Shell läuft. Wenn du verstehst, was die Shell mit deiner Eingabe macht, bevor überhaupt ein Programm startet, werden viele scheinbar seltsame Verhaltensweisen logisch.

Für jede Zeile, ob interaktiv eingetippt oder aus einem Skript gelesen, arbeitet die Bash dieselbe Folge ab:

flowchart TD
    A["1 · Zeile analysieren<br/>Befehl, Optionen, Argumente trennen"] --> B["2 · Befehl suchen<br/>Alias → Funktion → Builtin → PATH"]
    B --> C["3 · Umleitungen und Pipes einrichten<br/>> >> < 2> |"]
    C --> D["4 · Sonderzeichen ersetzen<br/>* ? {a,b} ~ $VAR $(...)"]
    D --> E["5 · Ablauf steuern<br/>&& || ; if"]
    E --> F["6 · Programm ausführen<br/>warten, Exit-Status merken"]
    F -->|nächste Zeile| A

Streng genommen passieren Schritt 3 und 4 in der Bash in einer etwas anderen Reihenfolge, aber als Denkmodell reicht diese Liste völlig. Die Schritte 3 und 4 bekommen auf den nächsten zwei Seiten je eine eigene Seite. Hier geht es um die Schritte 1, 2, 5 und 6.

Die Shell zerlegt die Zeile an Leerzeichen in Wörter. Das erste Wort ist der Befehl, der Rest sind Optionen und Argumente:

Terminal-Fenster
ls -lt /etc

ls ist der Befehl, -lt sind zwei Optionen, /etc ist das Argument. Für die Shell sind das drei Wörter, die sie dem Programm ls weiterreicht. Was eine Option bedeutet, weiss nur ls selbst. Merke dir: Leerzeichen trennen Wörter. Genau deshalb sind Dateinamen mit Leerzeichen heikel, dazu mehr bei Metazeichen und Quoting.

Die Bash sucht den Befehl in dieser Reihenfolge und nimmt den ersten Treffer:

  1. Alias: eine Abkürzung, die du selbst definiert hast (alias ll='ls -alF').
  2. Funktion: eine Shell-Funktion, zum Beispiel aus ~/.bashrc.
  3. Builtin: ein Befehl, der in die Shell eingebaut ist, etwa cd, echo, pwd, type, export.
  4. Programm im PATH: ein ausführbares File in einem der Verzeichnisse, die in der Variable PATH stehen, von links nach rechts durchsucht.

Findet sie nichts, kommt die bekannte Meldung bash: xyz: command not found.

Hands-on: Der Befehl type sagt dir, was die Shell gefunden hat.

Terminal-Fenster
type cd
type ls
type ll
type -a echo

Was erwartest du? cd ist ein Builtin, ls wird auf Ubuntu als Alias für ls --color=auto gemeldet, ll ist ebenfalls ein Alias. type -a zeigt alle Treffer: echo gibt es als Builtin und als Programm /usr/bin/echo. Das Builtin gewinnt.

Warum muss cd ein Builtin sein? Ein externes Programm läuft als eigener Prozess und kann das Arbeitsverzeichnis seines Elternprozesses (der Shell) nicht ändern. Das Verzeichnis zu wechseln geht nur, wenn die Shell es selbst tut.

Terminal-Fenster
echo "$PATH"

Du siehst eine Liste von Verzeichnissen, getrennt durch Doppelpunkte, auf Ubuntu etwa:

/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin

Der Reihe nach wird in jedem dieser Verzeichnisse nach einer Datei mit dem Befehlsnamen gesucht. Darum liegt /usr/local/bin vor /usr/bin: Selbst installierte Programme sollen die Systemversion überstimmen können.

Eigene Skripte gehören nach ~/bin. Ubuntu fügt dieses Verzeichnis beim Login automatisch zum PATH hinzu, sofern es existiert:

Terminal-Fenster
mkdir -p ~/bin
cat > ~/bin/hallo <<'EOT'
#!/bin/bash
echo "Hallo von $(whoami)"
EOT
chmod +x ~/bin/hallo
hallo

Falls die Shell hallo nicht findet, war ~/bin beim Login noch nicht da. Melde dich einmal ab und wieder an oder lade ~/.profile neu mit source ~/.profile. Mehr dazu auf der Seite Variablen und Umgebung.

Die Shell startet das Programm, wartet, bis es fertig ist, und merkt sich dessen Exit-Status: eine Zahl zwischen 0 und 255. Die Konvention ist einfach: 0 heisst Erfolg, alles andere heisst Fehler. Den Status des letzten Befehls findest du in der Variable $?.

Hands-on:

Terminal-Fenster
ls /etc/passwd
echo $?
ls /gibtsnicht
echo $?
true
echo $?
false
echo $?

true und false sind winzige Programme, die nichts anderes tun, als 0 beziehungsweise 1 zurückzugeben. Sie sind praktisch zum Testen.

Mit dem Exit-Status kannst du direkt auf der Befehlszeile Entscheidungen treffen:

Operator Bedeutung
a ; b b läuft immer, egal wie a ausgeht
a && b b läuft nur, wenn a Erfolg hatte (Status 0)
a || b b läuft nur, wenn a fehlgeschlagen ist

Hands-on:

Terminal-Fenster
mkdir /tmp/test && cd /tmp/test && pwd
mkdir /tmp/test || echo "gibt es schon"
cd ~ ; echo "bin wieder zuhause"
sudo apt update && sudo apt upgrade -y

Die letzte Zeile zeigt, warum && so beliebt ist: Das Upgrade soll nur laufen, wenn die Paketlisten erfolgreich geladen wurden. Mit ; würde es trotz Fehler starten.

Die Bash ist nicht die einzige Shell, sondern nur die auf Linux üblichste:

Shell Pfad Bemerkung
Bourne Shell sh /bin/sh Das Original von 1979. Heute ein Verweis auf eine andere Shell.
Bash /bin/bash «Bourne Again Shell», Standard auf fast allen Linux-Systemen.
Dash /bin/dash Klein und schnell, auf Ubuntu und Debian steckt sie hinter /bin/sh.
Zsh /bin/zsh Viele Komfortfunktionen, Standard auf macOS.
Fish /usr/bin/fish Eigene, nicht POSIX-kompatible Syntax, sehr einsteigerfreundlich.

Alle arbeiten nach denselben sechs Schritten, aber Syntax und Sonderzeichen unterscheiden sich im Detail. Wichtig für dich: Auf Ubuntu ist /bin/sh nicht die Bash, sondern Dash. Ein Skript mit #!/bin/sh darf darum nur das verwenden, was der POSIX-Standard vorschreibt. Bash-Spezialitäten wie [[ ]] oder Arrays funktionieren dort nicht. In diesem Kurs schreibst du Skripte deshalb immer mit #!/bin/bash.

Hands-on: Welche Shell läuft gerade?

Terminal-Fenster
echo "$SHELL"
ps -p $$
ls -l /bin/sh
cat /etc/shells

$SHELL enthält deine Login-Shell, wie sie in /etc/passwd eingetragen ist. Das ist nicht zwingend die Shell, die gerade läuft. Verlässlich ist ps -p $$: $$ ist die Prozess-ID der aktuellen Shell, und ps zeigt dir, welches Programm dahintersteckt. Probiere es aus:

Terminal-Fenster
sh
ps -p $$
echo "$SHELL"
exit

In der sh zeigt ps jetzt sh (eigentlich Dash), während $SHELL unverändert /bin/bash meldet. Mit exit bist du zurück in der Bash.

/bin/sh ist ein Symlink auf dash. Die Login-Shell neuer Benutzer ist /bin/bash, bei useradd ohne Optionen allerdings /bin/sh, deshalb adduser verwenden oder -s /bin/bash angeben.

Lösung anzeigen
Terminal-Fenster
type cd ls ll which type python3
# Antwort 1: cd und type sind Builtins, ls und ll Aliase, which und python3 Programme in /usr/bin.
echo "$PATH" | tr ':' '\n'
echo "$PATH" | tr ':' '\n' | wc -l
# Antwort 2: 9 Verzeichnisse (Standard-Ubuntu, plus ~/bin, wenn vorhanden).
printf '#!/bin/bash\ndate +%%A\n' > ~/bin/wochentag
chmod +x ~/bin/wochentag
wochentag
ls /etc > /dev/null; echo $?
ls /nirgends; echo $?
# Antwort 4: 0 und 2.
mkdir -p ~/labtest && cd ~/labtest && pwd
sh
ps -p $$
exit
# Antwort 6: In der Spalte CMD steht sh, dahinter steckt laut ls -l /bin/sh die Dash.

Selbstcheck · 5 Fragen

1. In welcher Reihenfolge sucht die Bash einen Befehl?
2. Warum ist cd ein Builtin und kein Programm?
3. Welcher Exit-Status bedeutet Erfolg?
4. Was macht `a || b`?
5. Welche Aussagen stimmen? (mehrere Antworten möglich)