Bash-Skripte
(GNU Bash 5.1.16 unter Linux Mint 21)
Übersicht
Variablen und Kommentare
Eingabe und Ausgabe
Pseudokommando
Benutzerdefinierte Kommandos erzeugen
Argumente an das Skript übergeben und auslesen
Bash ist nicht nur ein Kommandozeileninterpreter, sondern auch eine Programmiersprache. Somit lassen sich nicht nur einzelne Kommandos mit Bash ausführen, sondern auch Programme, die in Bash geschrieben sind. Bash eignet sich besonders zur Analyse und Administration von Rechnern und Netzwerken, da Systemprogramme sehr einfach aufgerufen werden können. Mit Bash können leicht kurze Skripte beispielsweise für die Stapelverarbeitung von Daten geschrieben werden. Aufgrund seiner beschränkten Einsatzmöglichkeiten und der stellenweise nicht besonders intuitiven Syntax ist Bash für Einsteiger in die Programmierung eher weniger geeignet.
Beispielsweise unterstützt Bash keine Gleitkomma-Arithmetik, keine mehrdimensionalen Arrays und keine mehrzeiligen Kommentare.
Zur Demonstration wird das folgende kurze Skript, das lediglich eine Zählschleife enthält, zunächst als Datei foobar gespeichert:
clear # leert das Terminalfenster
for ((i=1; i<=10; i++)); do
echo $i
done
Mit dem Kommando bash foobar
wird es nun ausgeführt. Da Skripte, die ohne explizite Angabe eines Interpreters direkt von der Standard-Shell ausgeführt werden können, lässt sich das Programm auch mit dem Kommando ./foobar
starten. Dies funktioniert allerdings nur, wenn die Datei zuvor ausführbar gemacht wurde. Dies geschieht mit dem Kommando chmod +x foobar
.
Offenbar ist die Standard-Shell unter Linux Mint ohnehin bash (was das Kommando echo $SHELL
bestätigt), denn wenn man das Skript mit dem Kommando dash foobar
aufruft, kommt es zu einem Fehler, da dash und bash nicht kompatibel sind. Auf anderen Rechnersystemen kann die Standard-Shell aber eine andere als bash sein, weshalb es aus Gründen der Portabilität sinnvoll ist, im Skript selbst zu vermerken, mit welcher Shell es ausgeführt werden soll. Dies geschieht mit dem sogenannten Shebang in der ersten Zeile eines Skriptes, bei dem nach den Zeichen #! der Pfad zu dem gewünschten Interpreter angegeben wird (dies gilt auch für andere Programmiersprachen):
#!/usr/bin/bash
clear # leert das Terminalfenster
for ((i=1; i<=10; i++)); do
echo $i
done
Wird der Shebang jetzt probehalber auf #!/usr/bin/dash geändert, kommt es bei der Ausführung des Skriptes mit ./foobar
wieder zu einer Fehlermeldung. Wird das Skript so nun aber mit bash foobar
ausgeführt, kommt es zu keinem Fehler, da der Interpreter explizit angegeben und damit der Shebang bei der Ausführung ignoriert wurde.
Außerdem ist es häufig sinnvoll, dem Dateinamen eine Erweiterung zu geben, über die der Datentyp der Datei sichtbar wird. Im Fall von Shell-Skripten ist das die Endung .sh.
Variablen und Kommentare
Die Wertzuweisung zu einer Variablen erfolgt mit dem =-Operator, wobei keine Leerzeichen vor und nach dem Operator zulässig sind.
Zulässige Zeichen für den Bezeichner der Variablen sind die Buchstaben a-z, A-Z, Zahlen und der Unterstrich _. Der Bezeichner darf nicht mit einer Zahl beginnen. Groß- und Kleinschreibung von Bezeichnern wird unterschieden.
#!/usr/bin/bash
# Definition von Variablen
a=42 # zulässig
A=50 # zulässig
_b_=255 # zulässig
: ' Diese Bezeichner sind unzulässig:
dö=18
50cents=100
a-100=200
'
echo $a; echo $aa
a=Osterhase
echo $a
unset a
for ((i=1; i<=10; i++)); do echo $i; echo "-"; done
exit
echo "Diese Zeile wird nach exit nicht mehr ausgegeben."
Aufgerufen („expandiert“) wird eine Variable durch vorangestelltes $-Zeichen (Zeile 12). Der Aufruf unbekannter Variablen wird kommentarlos ignoriert (echo $aa erzeugt hier nur einen Zeilenvorschub).
In Zeile 13 wird der Variable a, die bislang eine Zahl enthielt, ein String zugewiesen, was durch implizite Typumwandlung der Bash eine Typverletzung umgeht. Mit anderen Worten, in Bash lässt sich der Datentyp einer Variable durch Zuweisung eines entsprechenden Wertes problemlos ändern.
Mit unset kann eine Variable wieder gelöscht werden (Zeile 15).
Das Semikolon am Ende einer Anweisung ist nur dann nötig, wenn mehrere Anweisungen in einer Zeile stehend voneinander abgegrenzt werden müssen (Zeile 17).
Das Kommando exit beendet den Programmablauf. Alle folgenden Zeilen werden nicht mehr ausgeführt (Zeile 19).
Im obigen Beispiel wurden einzeilige Kommentare mit # eingeleitet. Mehrzeilige Kommentare existieren in Bash nicht, können aber mit obigem Workaround erreicht werden (Zeilen 7 bis 11).
Einrückungen sind im Bash-Quelltext vorteilhaft für die Übersichtlichkeit, aber nicht vorgeschrieben.
Anmerkung: Bash bringt im Gegensatz zu vielen anderen Programmiersprachen nur wenige eigene Sprachelemente mit (sog. builtins, s. hier). Dies wird durch die Tatsache kompensiert, dass Bash auf vorinstallierte Kommandos aus /usr/bin
zurückgreifen kann.
Eingabe und Ausgabe
Die zu verarbeitenden Daten können aber nicht nur aus Variablen gelesen, sondern auch vom Benutzer direkt eingeben werden. Dazu dient das Kommando read. In diesem Fall wird es mit der Option -p (prompt) aufgerufen, womit der Eingabe ein kurzer Text vorangestellt werden kann.
Die Ausgabe erfolgt dann mit dem Kommando echo.
Mit der Option -n wird der Zeilenumbruch am Ende des übergebenen Textes unterdrückt.
read -p "Eingabe: " eingabe
echo Die Eingabe war: $eingabe
echo -n "Foo"; echo -n "Bar"
Normalerweise erfolgt die Ausgabe über den Kanal stdout (standard output), also das Terminal. Die Eingabe erfolgt über den Kanal stdin (standard input), also die Tastatur (siehe auch UbuntuUsers-Wiki). Es ist über eine Umleitung mit den Operatoren > und < aber auch möglich, in eine Datei zu schreiben oder aus einer Datei zu lesen, was folgende Beispiele verdeutlichen:
echo "Dies ist die erste Zeile." > ./foobar.txt # löscht die Datei und schreibt den String in die Datei
echo "Dies ist noch eine Zeile." >> ./foobar.txt # fügt den String ans Ende der Datei an
read a < ./foobar.txt # liest eine Zeile aus der Datei
echo $a
echo
contents=$(< ./foobar.txt) # liest die gesamte Datei ein
echo "$contents"
Pseudokommando
Das Zeichen : repräsentiert ein Pseudokommando, das nichts tut und damit z. B. als Platzhalter verwendet werden kann, um ein Skript valide zu schreiben (und damit ausführbar zu halten), auch wenn der Code, für den der Platzhalter steht, noch nicht geschrieben ist.
if [[ $str == foo ]];
then : # der eigentliche Code an dieser Stelle kommt später
else echo "not foo"
fi
Benutzerdefinierte Kommandos erzeugen
Bash-Skripte können selbst zu Shell-Kommandos werden, indem man sie mit Root-Rechten unter /usr/bin abspeichert (ohne Dateinamenserweiterung .sh). Der Ordner /bin ist eine Verknüpfung auf /usr/bin und kann daher ebenfalls verwendet werden.
Bei der Namenswahl ist darauf zu achten, dass es kein Kommando mit dem gewünschten Namen bereits gibt. Dies lässt sich mit type NAME
prüfen, das den Pfad zum Kommando NAME ausgibt, falls dieses existiert.
Beispiel:
type helloworld
(nicht vergeben)
sudo xed /usr/bin/helloworld
#!/usr/bin/bash
echo "Hallo, Welt!"
Anschließend wird das Skript ausführbar gemacht:
sudo chmod +x /usr/bin/helloworld
Nun kann es als Kommando aufgerufen werden:
helloworld
Argumente an das Skript übergeben und auslesen
Wie bei jedem anderen Shell-Kommando auch können an die Datei nun Argumente übergeben werden, die das Skript dann verarbeiten kann.
Zunächst wird das Skript helloworld folgendermaßen ergänzt:
#!/usr/bin/bash
echo "Hallo, Welt!"
echo "${0}" # gibt den Pfad zum Skript aus
echo "${@}" # gibt alle Argumente aus
echo "${#@}" # gibt die Anzahl der Argumente aus
for arg in "${@}"; do # gibt die Argument einzeln aus
echo $arg
done
for arg; do # gibt die Argument ebenfalls einzeln aus
echo $arg
done
Nun kann man das Skript mit ein paar beliebigen Argumenten aufrufen und erhält dann die entsprechende Ausgabe:
benutzer@rechner:~$ helloworld -a -b -cd Foobar
Hallo, Welt!
/usr/bin/helloworld
-a -b -cd Foobar
4
-a
-b
-cd
Foobar
-a
-b
-cd
Foobar
Zwar lassen sich die Argumente jetzt mit gewöhnlichen Prüfungen von Bedingungen parsen, dieser Ansatz ist jedoch relativ umständlich. Einfacher hingegen ist die Verwendung des Kommandos getopts, wie das folgende Beispiel illustriert.
In dem Skript wird eine while-Schleife über alle Rückgabewerte des Kommandos getopts durchgeführt (alle an das Skript übergebenen Argumente). An dieses Kommando wird als Argument ein String übergeben, der mit einem Doppelpunkt beginnt und alle Buchstaben enthält, die als Optionsschalter berücksichtigt werden sollen (hier a, b, c und d). Optionen, die ein weiteres Argument benötigen (beispielsweise einen Dateipfad oder einen sonstigen benutzerdefinierten Wert), folgt ein weiterer Doppelpunkt (hier bei b).
Bei jedem Durchlauf der Schleife werden die an das Skript übergebenen Argument der Reihe nach in die Variable option geschrieben und mit der case-Anweisung geprüft. Ein an eine Option gebundenes Argument wird in die festgelegte Variable OPTARG geschrieben; dieser Variablenname kann nicht frei gewählt werden.
Es können nun auch Optionen in der üblichen kombinierten Schreibweise wie beispielsweise -acb verwendet werden. Optionen, die ein weiteres Argument benötigen, müssen als letztes in einer solchen Kette notiert werden.
In diesem Beispiel wird nun lediglich das Vorhandensein einer bestimmten Option mit echo bestätigt. In einem realen Skript würde man an dieser Stelle natürlich die benötigte Funktionalität einer Option implementieren.
while getopts ':ab:cd' option; do
case "$option" in
a) echo "Option -a aktiv.";;
b) echo "Option -b aktiv, Argument: $OPTARG";;
c) echo "Option -c aktiv.";;
d) echo "Option -d aktiv.";;
*) echo "unbekanntes Argument"
esac
done