Bitte warten...

Bash-Skripte

(GNU Bash 5.1.16 unter Linux Mint 21)

Bash-Skripte werden mit dem Kommando bash ausgeführt. Häufig wird auch das Kommando sh angegeben, das unter Linux Mint (sowie in Debian und Ubuntu) aber auf die Shell dash (s. Wikipedia) verweist, die zwar kompatibel zu bash aber nicht identisch ist! Probe: readlink /bin/sh

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:

Code kopieren
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):

Code kopieren
#!/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.

Code kopieren
#!/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.

Code kopieren
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:

Code kopieren
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.

Code kopieren
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

Code kopieren
#!/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:

Code kopieren
#!/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 Argumente einzeln aus
  echo $arg
done

for arg; do  # gibt die Argumente ebenfalls einzeln aus
  echo $arg
done

Nun kann man das Skript mit ein paar beliebigen Argumenten aufrufen und erhält dann die entsprechende Ausgabe:

Code kopieren
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.

Code kopieren
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