Eine Shell ist ein Programm, mit dem du Befehle eingeben kannst, um mit dem Betriebssystem zu interagieren. Shells können grafisch sein; deine Desktop-Umgebung, die du mit Maus oder Trackpad bedienst, ist eine Shell. Wir konzentrieren uns auf Kommandozeilenschnittstellen, bei denen du Befehle eintippst und die Ergebnisse (meistens) im Terminal ausgegeben werden.
Es gibt viele verschiedene Kommandozeilen-Shells. Windows bietet cmd und powershell. Mac und Linux bieten „Unix-Shells“, von denen es viele gibt. Die Standardshell unter Mac ist zsh. csh ist eine alte Shell mit C-ähnlicher Syntax. bash ist eine der beliebtesten Shells, besonders unter Linux.
Eine der allerersten Shells ist die Bourne-Shell, sh. Mit der Zeit wurde diese Shell standardisiert, sodass sie auf jedem Unix-ähnlichen System bereitgestellt wird. Viele Shells basieren auf der Bourne-Shell und bieten oft zusätzliche interaktive oder Programmierfunktionen.
Bash basiert auf der Bourne-Shell. Deshalb heißt sie „Bourne-again shell“. Sie ist die Standardshell auf vielen Linux-Systemen. Bash bietet viele Annehmlichkeiten, die sie zu einer angenehmen interaktiven Shell machen, darunter:
In diesem Track geht es nicht darum, wie du bash als interaktive Shell benutzt. Es geht darum, zu lernen, Programme mit bash zu schreiben. Der Schwerpunkt liegt darauf, Probleme nur mit bash zu lösen, auch wenn es keine Einschränkungen gibt, externe Tools aufzurufen.
Bash liest Befehle aus ihrer Eingabe (meistens ein Terminal oder eine Datei). Jede gelesene Eingabezeile wird als Befehl behandelt, also als eine Anweisung, die ausgeführt werden soll. (Es gibt ein paar fortgeschrittene Fälle, etwa Befehle, die sich über mehrere Zeilen erstrecken; diese besprechen wir später.)
Bash teilt jede Zeile in Wörter auf, die durch ein Whitespace-Zeichen (Leerzeichen und Tabs) getrennt werden. Das erste Wort der Zeile ist der Name des Befehls, der ausgeführt werden soll. Alle übrigen Wörter werden zu Argumenten dieses Befehls (Optionen, Dateinamen usw.).
In den Beispielen unten zeigen wir dir einige interaktive Befehle, die du ausführen kannst.
# beginnt einen Kommentar.
Alle Wörter, die auf das Kommentarzeichen folgen, ignoriert die Shell; der Kommentar ist nur zum Lesen gedacht.$ kennzeichnet die Eingabeaufforderung der Shell.
Wenn du den Befehl kopierst, übernimm die Eingabeaufforderung nicht mit.# Note: It is always a good idea to run `ls` before `rm`
# to make sure you know what you are deleting.
$ rm * # Remove all files in the current directory.
# Make sure you run this in a temp directory or you can lose files!
$ ls # List files in the current directory (no output, no files).
$ touch a b c # Create files 'a', 'b' and 'c'.
$ ls # List files again; this time the outputs: 'a', 'b' and 'c'.
a b c
rm ist eine Anwendung, die die ihr übergebenen Dateien löscht.
* ist ein Glob-Zeichen, ein „Wildcard“-Muster, das auf alle Dateien im aktuellen Verzeichnis passt.
Über Globs sprechen wir später noch genauer.
Ist dir aufgefallen, dass zwischen a und b mehrere Leerzeichen stehen und zwischen b und c nur eines?
Wie viel Whitespace zwischen den Argumenten steht, spielt keine Rolle!
Zum Beispiel:
$ echo This is a test.
This is a test.
$ echo This is a test.
This is a test.
echo ist ein Befehl, der seine Argumente auf die Standardausgabe schreibt (in unserem Fall das Terminal).
Bash zerlegt den Befehl in fünf Wörter und ruft den Befehl echo mit vier Argumenten auf.
Die zusätzlichen Leerzeichen im zweiten Beispiel machen keinen Unterschied.
echo weiß nicht, wie viele Leerzeichen ursprünglich zwischen den Wörtern standen.
Wenn wir den zusätzlichen Whitespace behalten wollen, müssen wir den Satz als ein einziges Argument übergeben. Das geht mit Anführungszeichen:
$ echo "This is a test."
This is a test.
Anführungszeichen fassen alles zwischen ihnen zu einem einzigen Argument zusammen.
Das Argument ist This is a test. samt dieser konkreten Leerzeichen.
Beachte, dass die Anführungszeichen nicht Teil des Arguments sind: bash entfernt sie, bevor sie das eine Argument an echo übergibt.
Argumente werden durch Whitespace vom Befehlsnamen und voneinander getrennt. Das solltest du dir merken. Das Folgende ist zum Beispiel falsch:
$ [-f file]
bash: [-f: command not found
Damit soll geprüft werden, ob eine Datei namens file existiert.
Man könnte annehmen, dass Whitespace um [ und ] keine Rolle spielt, aber [ ist tatsächlich ein Befehl, und sein letztes Argument muss ] sein.
(Den Befehl [ behandeln wir später ausführlicher.)
Deshalb müssen wir [ von -f und ] von file trennen.
Sonst denkt bash, wir wollten einen Befehl namens [-f mit dem einzigen Argument file] ausführen.
Der korrekte Befehl trennt alle Argumente durch Whitespace:
$ [ -f file ]
(Wir erleben oft, dass dieses Verhalten verwirrt; viele halten [...] für „reine Syntax“ und meinen, die Leerzeichen weglassen zu können. [ ist aber ein Befehl, und der Whitespace ist Pflicht.)
Enthält unser Dateiname Whitespace oder andere Sonderzeichen, sollten wir ihn ebenfalls in Anführungszeichen setzen:
$ [ -f "my file" ]
Bash kennt mehrere verschiedene Arten von Befehlen: Aliase, Funktionen, Builtins, Schlüsselwörter und ausführbare Dateien. Sie alle sind Befehle und verhalten sich sehr ähnlich.
Standardmäßig sind Aliase nur in interaktiven Shells aktiviert, nicht in Shell-Skripten. Wir behandeln sie hier nicht.
Eine Funktion enthält Shell-Befehle und verhält sich sehr wie ein kleines Skript; sie kann sogar Argumente entgegennehmen und lokale Variablen anlegen. Wird eine Funktion aufgerufen, werden die Befehle darin ausgeführt. Funktionen behandeln wir später ausführlich.
Bash hat einige grundlegende Befehle eingebaut, zum Beispiel cd (Verzeichnis wechseln), echo (Ausgabe schreiben) und so weiter.
Man kann sie sich als bereits mitgelieferte Funktionen vorstellen.
Schlüsselwörter ähneln Builtins; der Hauptunterschied ist, dass Schlüsselwörter tatsächlich Bash-Syntax sind und nach besonderen Regeln geparst werden können.
Zum Beispiel ist [ ein Bash-Builtin, während [[ ein Bash-Schlüsselwort ist; beide dienen zum Prüfen verschiedenster Bedingungen.
Das Schlüsselwort [[ hat aber einige zusätzliche Funktionen.
Zum Beispiel stellt es den Operator =~ bereit, der reguläre Ausdrücke prüft.
$ [[ "hello world" =~ e.*o ]] # returns a "success" status
$ [ "hello world" =~ e.*o ]
bash: [: =~: binary operator expected
Die String-Vergleichsoperatoren < und > sind ein weiteres Beispiel dafür, wie Schlüsselwörter den Code anders parsen.
Innerhalb von [[...]] brauchen diese Operatoren keine besondere Behandlung.
$ if [[ "$USER" < "n" ]]; then
echo "$USER is in the first half of the alphabet"
fi
# => glennj is in the first half of the alphabet
Innerhalb von [...] musst du sie jedoch in Anführungszeichen setzen oder escapen, damit die Shell sie nicht als Umleitungsoperatoren interpretiert.
$ if [ "$USER" < "n" ]; then
echo "$USER is in the first half of the alphabet"
fi
# => bash: n: No such file or directory
$ if [ "$USER" \< "n" ]; then
echo "$USER is in the first half of the alphabet"
fi
# => glennj is in the first half of the alphabet
Die letzte Art von Befehl, die bash ausführen kann, ist eine ausführbare Datei.
Das sind ausführbare Dateien (Programme) im Dateisystem deines Computers, die du starten kannst.
(Ausführbare Dateien werden manchmal auch externe Befehle oder Anwendungen genannt.)
Ausführbare Dateien rufst du üblicherweise auf, indem du nur ihren Namen eintippst; der vollständige Pfad zur Datei ist meist nicht nötig.
Das funktioniert, weil bash eine vordefinierte Variable nutzt, die ihr hilft, den Ort gängiger ausführbarer Dateien zu finden.
Diese Variable heißt PATH.
Sie ist eine Menge von Verzeichnisnamen, die durch Doppelpunkte getrennt sind (z. B. /usr/bin:/bin).
Wird ein Befehl ohne Dateipfad angegeben (z. B. myprogram oder ls) und ist er kein Alias, keine Funktion, kein Builtin und kein Schlüsselwort, dann durchsucht bash die Verzeichnisse in PATH.
Die Verzeichnisse werden der Reihe nach von links nach rechts durchsucht, um zu sehen, ob sie eine ausführbare Datei mit dem angegebenen Namen enthalten.
Liegt die ausführbare Datei außerhalb eines bekannten Pfads, musst du ihren Dateipfad angeben.
Für die ausführbare Datei myprogram im aktuellen Verzeichnis nutzt du ./myprogram; liegt sie im Verzeichnis /opt/somedirectory, nutzt du /opt/somedirectory/myprogram.
Tipp: Mit dem Befehl type bekommst du Details über einen Befehl:
$ type rm
rm is hashed (/bin/rm)
# or it might return: `rm is /bin/rm`
$ type cd
cd is a shell builtin
Ein Skript ist eine Folge von Befehlen, die in einer Datei gespeichert sind. Bash liest die Datei und verarbeitet die Befehle der Reihe nach. Erst wenn der aktuelle Befehl beendet ist, geht sie zum nächsten über. Ausnahme: Ein Befehl wurde so angegeben, dass er asynchron (im Hintergrund) läuft. Mach dir darüber noch keine allzu großen Gedanken; wie das funktioniert, lernst du später.
Jedes Kommandozeilenbeispiel in diesem Leitfaden kannst du auch in einem Skript verwenden.
Ein Skript zu erstellen ist einfach. Lege zuerst eine neue Datei an und schreibe dies in die erste Zeile:
#!/bin/bash
Diese Zeile heißt Interpreter-Direktive (auch Hashbang oder Shebang genannt).
Sie legt fest, dass /bin/bash als Interpreter verwendet wird, wenn die Datei in einem Befehl als ausführbare Datei dient.
Wenn der Kernel des Betriebssystems eine nicht-binäre Datei ausführt, liest er die erste Zeile der Datei.
Beginnt die Zeile mit #!, ermittelt der Kernel daraus den Interpreter, an den er die Datei weiterreicht.
(Es gibt auch andere gültige Wege, siehe unten.)
Das #! muss ganz am Anfang der Datei stehen, ohne Leerzeichen oder Leerzeilen davor.
Die Befehle unseres Skripts stehen in den Zeilen darunter.
Tipp: Statt #!/bin/bash kannst du auch Folgendes verwenden:
#!/usr/bin/env bash
env durchsucht $PATH nach der ausführbaren Datei, die durch sein erstes Argument benannt wird (in diesem Fall „bash“).
Eine ausführlichere Erklärung dieser Technik und wie sie sich vom guten alten #!/bin/bash unterscheidet, findest du in dieser Frage auf StackOverflow.
Lass dich nicht von Skripten oder Beispielen im Internet täuschen, die /bin/sh als Interpreter verwenden.
sh ist nicht bash!
Bash selbst ist eine „sh-kompatible“ Shell, das heißt, sie kann die meisten sh-Skripte ausführen und übernimmt einen Großteil derselben Syntax.
Das Umgekehrte gilt jedoch nicht; einige Funktionen von bash schlagen in sh fehl oder verhalten sich unerwartet.
Tatsächlich gibt es nicht die eine sh-Shell.
Es gibt viele Shell-Implementierungen, die die POSIX-sh-Syntax unterstützen.
Je nach Betriebssystem führst du bei sh möglicherweise tatsächlich bash, ash, dash oder eine andere Shell aus.
Sobald die Skriptdatei erstellt ist, kannst du sie so ausführen:
$ bash myscript
In diesem Beispiel führen wir den Befehl bash aus und sagen ihm, das Skript zu lesen, das wir als Argument angegeben haben.
Wenn wir es so machen, ist die #!-Zeile nur ein Kommentar.
Alternativ können wir unseren Skripten Ausführungsrechte geben. Mit dieser Methode können wir das Skript wie jede andere ausführbare Datei starten, statt bash von Hand aufzurufen:
$ chmod +x myscript # Mark the file as executable.
$ ./myscript # Now, myscript can be executed directly
# instead of having to specify bash.
Wenn wir den Befehl so ausführen, nutzt das Betriebssystem (OS) die #!-Zeile, um zu bestimmen, welcher Interpreter zum Ausführen deines Skripts verwendet wird.
Der Inhalt dieser Lektion stammt aus Commands and Arguments im Bash Guide.