Kapitel 4
IN DIESEM KAPITEL
Wenn Sie mehrere Shell-Befehle immer wieder eingeben müssen, lohnt es sich meist schon, ein Skript anzulegen. Ein Skript ist eine Datei, in der die Befehle Zeile für Zeile aufgeschrieben werden. Diese Datei übergeben Sie als Argument an den Befehlsinterpreter bash
.
$ bash meinskript
Dadurch wird eine neue Shell bash
als Programm gestartet, das das Skript meinskript ausführt. Nachdem alle Befehle der Datei abgearbeitet sind, endet die Shell und kehrt zur aufrufenden Shell zurück.
Sie können die Skriptdatei ausführbar machen und direkt aufrufen. Dazu müssen Sie ihr Ausführungsrechte zuordnen. Dies erreichen Sie über den Befehl chmod
, der im Abschnitt 1 ausführlicher behandelt wird. Derzeit müssen Sie mir einfach glauben, dass der folgende Befehl die Datei meinskript ausführbar macht:
$ chmod 755 meinskript
Nun kann die Datei direkt aufgerufen werden, ohne dass bash
vorangestellt wird.
Um zu vermeiden, dass versehentlich ein boshaft untergeschobenes Skript ausgeführt wird, muss es sich entweder in einem der Verzeichnisse befinden, die durch die Umgebungsvariable PATH
definiert werden oder es muss mit dem Dateinamen auch der Pfad genannt werden. Dazu muss nicht der komplette Pfad genannt werden. Falls das Skript im aktuellen Arbeitsverzeichnis liegt, genügt es, den Punkt und einen nachfolgenden Verzeichnistrenner voranzustellen.
$ ./meinskript
Da für die Ausführung in beiden Fällen eine zusätzliche Shell aufgerufen wird, sind Variablen, die in der Skriptdatei gesetzt werden, nach dem Aufruf nicht mehr verfügbar, selbst wenn sie exportiert wurden.
Es gibt aber eine Möglichkeit, eine Skriptdatei aufzurufen, ohne dass eine Extra-Shell gestartet wird. Dazu verwenden Sie einen Punkt anstelle des Shell-Aufrufs.
$ . meinskript
Nun wird das Skript meinskript von der Shell ausgeführt, in der Sie sich gerade befinden. Falls Sie den Punkt zu mager finden, können Sie aber auch den Befehl source
verwenden, der das Gleiche tut.
Die erste Zeile können Sie nutzen, um den Interpreter festzulegen. Dies könnte beispielsweise eine andere Shell als bash
sein. Sie können aber auch andere Interpreter verwenden, wie beispielsweise Perl oder Python.
Der Interpreter wird im Kommentar in der ersten Zeile festgelegt. Auf das Kommentarzeichen müssen direkt ein Ausrufezeichen und dann der genaue Pfad des Interpreters folgen. Für die Bash wäre dies:
#!/bin/bash
Woher wissen Sie, dass bash
im Verzeichnis /bin/bash liegt? Dazu rufen Sie den Befehl which
auf. Auf die gleiche Weise können Sie herausfinden, wo sich der Interpreter der Sprache Perl befindet.
$ which bash
/bin/bash
$ which perl
/usr/bin/perl
Woher wissen Sie, dass das auf anderen Linux-Rechnern auch dort ist? Weil es in diesen Fällen Standards gibt. Solche Programme liegen in /bin oder /usr/bin.
Das Kommentarzeichen in einer Skriptdatei ist das Doppelkreuz (#). Alles, was in der gleichen Zeile dahinter steht, geht den Interpreter nichts an, hilft Ihnen aber später, wenn Sie sich nicht mehr erinnern können, wozu dieses Skript gut sein sollte und wie es arbeitet.
# Alles fertig, nun noch die Ergebnisse anzeigen:
ls -l # zeige alle Dateien mit allen Details an
Wenn ein Shell-Kommando länger als eine Zeile wird, können Sie den Befehl aufspalten, indem Sie einen einzelnen Backslash (\ ) an das Ende der Zeile setzen. Achten Sie darauf, dass kein Leerzeichen hinter dem Backslash steht. Im folgenden Beispiel hat der Befehl cat
(siehe Abschnitt 1) vier Dateien auf dem Bildschirm ausgegeben:
cat datei1 datei2 \
datei3 datei4
Die Shell kann Texte in Variablen speichern. Die Variablen erhalten ihren Wert durch eine Zuweisung. Links steht die Variable, es folgt ein Gleichheitszeichen und dann der Text, der in der Variablen gespeichert werden soll.
$ WETTER=“Sonnenschein, Windstärke 2, keine Wolken”
Der Befehl echo
tut das, was man von seinem Namen erwartet: Er wiederholt das, was man ihm als Parameter übergibt. Wenn Sie den Inhalt einer Variablen anzeigen wollen, müssen Sie dieser ein Dollarzeichen voranstellen:
$ echo $WETTER
Sonnenschein, Windstärke 2, keine Wolken
$
Normalerweise sind Variablen nur lokal innerhalb einer Shell zugreifbar. Wird aus der Shell eine weitere Shell oder ein anderes Programm gestartet, kann dieses auf die Variable nicht mehr zugreifen.
$ WETTER=“Sonnenschein, Windstärke 2, keine Wolken”
$ bash
$ echo $WETTER
$ exit
$ echo $WETTER
Sonnenschein, Windstärke 2, keine Wolken
$
Manche Variablen dienen aber zur Konfiguration. Solche Variablen sollen nicht nur für die aktuelle Shell, sondern auch für jedes Programm gelten, das von der Shell gestartet wird. Um dies zu erreichen, exportieren Sie diese Variable mit dem Befehl export
. Solche exportierten Variablen nennt man Umgebungsvariablen (engl. environment variables):
$ export WETTER
$ bash
$ echo $WETTER
Sonnenschein, Windstärke 2, keine Wolken
$
Sie können das Setzen und Exportieren der Variablen aber auch in einem Kommandoschritt ausführen:
$ export WETTER=“Sonnenschein, Windstärke 2, keine Wolken”
Damit wird die Shell-Variable WETTER
zur Umgebungsvariablen, die von jedem Prozess gelesen werden kann, der von dieser Shell aus gestartet wird.
Nicht nur Sie können Umgebungsvariablen erzeugen. Das ganze System ist voll davon. Sie können sich eine Liste der Umgebungsvariablen mithilfe des Befehls env
anzeigen lassen. Damit Sie überhaupt eine Chance haben, etwas zu finden, sollten Sie die Ausgabe durch more
oder less
schicken. Mit dem Befehl grep
können Sie auch nach bestimmten Variablen filtern. Das folgende Beispiel zeigt, wie alle Umgebungsvariablen angezeigt werden, die die Silbe LANG enthalten:
$ env | grep LANG
LANGUAGE=de_DE
GDM_LANG=de_DE
LANG=de_DE.UTF-8
$
Das Betriebssystem hat diverse Informationen in Umgebungsvariablen abgelegt, die von Skripts verwendet werden können.
Die Variable LANG
wird auch als Language-Variable bezeichnet. Sie stellt die Sprache des Systems ein. Neben der Sprache von Manpages betrifft dies beispielsweise auch die Darstellung von Uhrzeit und Datum.
Die Variable PATH
enthält die Liste aller Verzeichnisse, auf denen die ausführbaren Programme zu finden sind. Die Verzeichnisse sind durch einen Doppelpunkt voneinander getrennt:
$ echo $PATH
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
Wenn Sie auf diesem Computer also ein Programm aufrufen, wird es die Verzeichnisse in dieser Reihenfolge durchsuchen.
Wenn Sie erreichen wollen, dass auch das aktuelle Verzeichnis nach Befehlen durchsucht wird, nehmen Sie den Punkt in den Pfad auf. Standardmäßig ist er dort nämlich nicht mehr, was dazu führt, dass Programmierer beim Test ihrer eigenen Programme immer ./
vor den Programmnamen stellen müssen, damit sie gefunden werden.
Wen das stört, kann das wie folgt ändern:
$ export PATH=$PATH:.
Der Punkt wurde aus dem PATH
entfernt, weil es damit möglich ist, Programme an ungeschützten Stellen unterzubringen. Die normalen Pfade von PATH
sind für den normalen Anwender nicht beschreibbar.
Was links neben dem Cursor steht, wird als Prompt bezeichnet. Beim normalen Benutzer ist dies ein Dollarzeichen. Links daneben sind oft weitere Angaben wie der Benutzername, der Rechnername und der aktuelle Pfad eingeblendet. Diese Informationen stehen in der Umgebungsvariablen PS1.
Wollen Sie in Ihrem Prompt auch solche Informationen wie Hostname, Benutzer oder Pfad unterbringen, setzen Sie dazu \u
für den Benutzernamen, \h
für den Rechnernamen und \w
für den Pfad in Ihren PS1 ein. Falls in Ihrem Prompt plötzlich eine Schlange auftaucht, handelt es sich um eine Tilde (˜), die eine Abkürzung für das Heimatverzeichnis des aktuellen Benutzers ist. Um ein Dollarzeichen zu erzeugen, wie es im Prompt bei normalen Benutzern Standard ist, müssen Sie dem einen Backslash voranstellen, weil das Dollarzeichen ansonsten für den Zugriff auf Variableninhalte reserviert ist. Das folgende Beispiel bildet die meistverwendete Kombination des Standardprompts nach:
$ PS1=“\u@\h:\w\$ ”
arnold@server:˜/my/texte/tex/buch$
Neben dem Prompt PS1
gibt es noch PS2
für den Sekundärprompt. Er taucht immer auf, wenn Sie mitten in einem Befehl drücken, die Shell aber erkennt, dass der Befehl noch nicht abgeschlossen ist. Das passiert, wenn Sie zu einem for
noch kein done
eingegeben oder die abschließenden Anführungszeichen oder Klammern vergessen haben. Normalerweise steht in PS2
schlicht ein Größerzeichen.
Jeder Benutzer eines Linux-Systems hat seinen eigenen Bereich, in dem alle seine Daten liegen. Dieses Verzeichnis wird in der Datei /etc/passwd festgelegt. Den jeweiligen Pfad zum eigenen Benutzerverzeichnis findet jeder Benutzer in der Umgebungsvariablen HOME.
Die Umgebungsvariable EDITOR steuert, welcher Editor aufgerufen wird, wenn bei Programmabläufen zwischendurch editiert werden muss. Standardmäßig wird Linux in diesen Fällen nano
aufrufen. Da ich den vim
bevorzuge, steht bei mir beispielsweise:
export EDITOR=vi
Shell-Skripte sind nicht nur eine Aneinanderreihung von Befehlen. Es gibt eine Skript-Sprache, die wie andere Programmiersprachen Abfragen, Schleifen und Funktionen anbietet. Die wichtigsten Aspekte seien hier vorgestellt.
Für Abfragen steht das Schlüsselwort if
zur Verfügung. Hinter dem if
steht eine Bedingung. Ist diese wahr, wird der Bereich zwischen then
und fi
ausgeführt. Optional gibt es ein else
. Dieser Zweig wird ausgeführt, wenn die Bedingung nicht zutrifft. Eine sehr einfache Abfrage sieht so aus:
if true
then
echo “gut”
else
echo “schlecht”
fi
Wenn Sie diese Befehlssequenz eingeben, erscheint auf dem Bildschirm das Ergebnis »gut«. Tauschen Sie true
gegen false
aus, erhalten Sie die Ausgabe »schlecht«.
if
eingeleitet.then
ein Block von Befehlen eingeleitet.else
.else
folgen weitere Zeilen von Befehlen, die ausgeführt werden sollen, wenn die Bedingung nicht erfüllt ist.fi
.Es ist aber auch möglich, die Anweisung in einer Zeile einzugeben. Dann muss hinter dem booleschen Ausdruck und jeder Anweisung ein Semikolon stehen.
if true; then echo "gut"; else echo "schlecht"; fi
Die Bedingungen werden durch den Befehl test
ausgewertet. So prüft man, ob eine Datei namens hugo existiert, mit dem folgenden Befehl:
if test -f hugo
then
echo “hugo ist da!”
fi
Wer allerdings andere Programmiersprachen kennt, verwendet statt des Befehls test
lieber ein Klammernpaar. Darauf nimmt die Bash Rücksicht. Allerdings werden statt runder Klammern, wie in den meisten Programmiersprachen, bei Bash eckige Klammern verwendet. Und noch eine Besonderheit: Die eckigen Klammern möchten gern von Leerzeichen umgeben sein.
if [ -f *.conf ]
then
echo “Konfigurationsdatei gefunden”
fi
Es kann geprüft werden, ob eine Datei oder ein Verzeichnis existiert. Dazu können auch Wildcards verwendet werden.
test -f
Name: Existiert eine Datei namens Name?test -d
Name: Existiert ein Verzeichnis namens Name?Als Bedingungen können auch Zeichenketten verglichen werden. Diese liegen typischerweise in Umgebungsvariablen.
test
Str: Ist Str eine nicht leere Zeichenkette?test
Str1 =
Str2: Sind die Zeichenketten Str1 und Str2 gleich?test
Str1 !=
Str2: Sind die Zeichenketten Str1 und Str2 ungleich?ERG = $(grep Meier *.txt)
if [ $ERG ]
then
echo “Meier ist dabei”
fi
Es ist auch möglich, Zahlenwerte zu vergleichen. Diese beziehen sich in der Regel auch auf Umgebungsvariablen.
test
Nr1 -eq
Nr2: Ist die Zahl Nr1 gleich Nr2?test
Nr1 -ne
Nr2: Ist die Zahl Nr1 ungleich Nr2?test
Nr1 -ge
Nr2: Ist die Zahl Nr1 größer oder gleich Nr2?test
Nr1 -gt
Nr2: Ist die Zahl Nr1 größer als Nr2?test
Nr1 -le
Nr2: Ist die Zahl Nr1 kleiner oder gleich Nr2?test
Nr1 -lt
Nr2: Ist die Zahl Nr1 kleiner als Nr2?Zur Prüfung auf Gleichheit können neben -eq
und -ne
auch = und != verwendet werden. Sogar Fans des doppelten Gleichheitszeichens kommen hier auf ihre Kosten.
x=4
y=3
if [ $y -lt $x ]; then echo "ja"; fi
if [ $y != $x ]; then echo "ja"; fi
if [ ! $y = $x ]; then echo "ja"; fi
if [ ! $y == $x ]; then echo "ja"; fi
Die Kleiner- und Größerzeichen können auch verwendet werden, aber mit einem kleinen Kniff. Dann muss die Bedingung in runde Klammern gestellt werden. So wie die eckigen Klammern den Befehl test
ersetzen, ersetzen die runden Klammern den Befehl let
.
x=4
y=3
if (( $y < $x )); then echo "ja"; fi
Um eine Wiederholung zu realisieren, wird statt der if
-Anweisung das Schlüsselwort while
verwendet. Schon kleine Kinder können zählen. Letztlich ist das Zählen eine typische Wiederholung in einer Schleife. Auf den Startwert wird wiederholt 1 aufaddiert, bis der Zielwert erreicht wird.
Beim Zählen benötigt man eine Variable, in der der aktuelle Zählerstand steht. Diese Variable kann heißen, wie sie will. Ich habe hier den Namen i verwendet.
i=0
while test $i -lt 10
do
echo $i
let i=$i+1
done
while
folgt der boolesche Ausdruck, dass i kleiner als 10 bleiben muss.do
folgen die zu wiederholenden Befehle.done
markiert das Ende der zu wiederholenden Befehle.let
wird eine numerische Berechnung eingeleitet.Der Befehl test
ist für Programmierer gewöhnungsbedürftig. Sie mögen lieber Größer- und Kleinerzeichen. Damit diese nicht als Umleitungen interpretiert werden, helfen doppelte runde Klammern.
Die while
-Zeile im vorigen Listing können Sie durch die while
-Zeile im folgenden Listing ersetzen. Beide Zeilen tun dasselbe. Darum ist es Geschmackssache, welche Sie verwenden wollen.
while (( $i < 10 ))
Natürlich kann man auch Dateien oder Strings abfragen, also alles verwenden, was die oben genannten booleschen Ausdrücke hergeben.
Die for
-Schleife bearbeitet eine Liste, typischerweise von Dateinamen. Dazu können Sie Dateinamen hinter dem Befehl auflisten werden oder aber, wie eher üblich, mit einer Wildcard-Konstruktion mehrere Dateien erfassen.
Nehmen wir als Beispiel, dass wir alle Textdateien eines Verzeichnisses, die auf .txt enden, umbenennen wollen. Diese Dateien sollen alle zusätzlich die Endung .bak erhalten. Um den Befehl mv
, der die Umbenennung vornimmt, baut man eine Schleife. Die folgende Anweisung führt zu einer solchen Massenumtaufe:
for datei in *.txt
do
mv $datei $datei.bak
done
Die Shell ermittelt zunächst alle Dateinamen, die dem angegebenen Namensmuster entsprechen, also auf .txt enden, und ersetzt sie intern durch eine Liste der Namen. Das entspräche beispielsweise der folgenden Schleife, wenn im aktuellen Verzeichnis die Dateien namen.txt, unfug.txt und witze.txt existierten:
for variable in namen.txt unfug.txt witze.txt
do
mv $variable $variable.bak
done
Innerhalb der Schleife wird nun der eigentliche Befehl aufgerufen. Als Argument wird die Variable verwendet, die vor dem in
angegeben wurde. Bei jedem Durchlauf wird ihr Inhalt durch den nächsten Namen, der hinter dem in
steht, ersetzt. Als Trennzeichen betrachtet die Schleife jedes Leerzeichen. Auf den Inhalt der Variablen greift man durch das Voranstellen eines Dollarzeichens zu.
Die for
-Anweisung ist erst beendet, wenn der Befehl done
erfolgte, weil Sie innerhalb der Schleife mehr als einen Befehl ausführen könnten.
Insbesondere bei der Auswertung von Parametern wird die Fallunterscheidung case
gern eingesetzt. Das folgende Beispiel betrachtet die Variable arg
und unterscheidet drei Fälle.
case $arg in
stop) echo "Halt!" ; return 1 ;;
-*) echo "Option: $arg" ;;
*) echo "Argument: $arg" ;;
esac
arg
einfach das Wort stop.arg
mit einem Minuszeichen beginnt.Sie können auch ein Shell-Skript mit Parametern aufrufen. Aber wie kann das Skript erfahren, welche Parameter der Aufrufer übergeben hat? Der Inhalt der Pseudovariablen $#
gibt an, wie viele Parameter es überhaupt gibt. Den ersten Parameter findet das Skript in $1
, den zweiten in $2
und so weiter. Es fehlt Ihnen $0
? Darin befindet sich der Name des Skripts selbst.
Als Beispiel schreiben wir ein Skript tausche, das zwei Argumente erwartet und diese umdreht. Das ist schnell erledigt:
echo $2 $1
Nun soll das Skript noch prüfen, ob wirklich zwei Parameter übergeben werden. Zum Prüfen wird der Befehl if
verwendet. Es folgt die eckige Klammer, in der eine Bedingung steht. Trifft diese zu, wird der Zweig fortgesetzt, der mit then
eingeleitet wird. Die Abfrage endet mit dem Befehl fi
. Mit dem Befehl else
kann ein Zweig eingebaut werden, der durchlaufen wird, falls die Bedingung nicht zutrifft.
# Skript tauscht seine Parameter
if [ $# = 2 ]
then
echo $2 $1
else
echo "Falsche Parameterzahl"
fi
Wenn Sie alle Parameter auswerten wollen, die der Benutzer übergibt, muss das Skript in der Lage sein, diese nacheinander abzuarbeiten. Im Beispiel werden die übergebenen Werte in einer Schleife einfach nacheinander auf dem Bildschirm wiedergegeben.
In der rechteckigen Klammer wird der erste Parameter als Bedingung verwendet. Das Skript schaut, ob der String nicht leer ist. Dann ist die Bedingung erfüllt und die Schleife wird die Befehle zwischen do
und done
ausführen.
Der Befehl echo
gibt den Parameter aus. Der Befehl shift
schiebt die Parameter einen Parameter weiter. Der erste Parameter ist verarbeitet und wird entfernt. Der zweite Parameter wird zum ersten und der dritte Parameter zum zweiten. Solange es also noch Parameter gibt, wird die Schleife wiederholt.
while [ $1 ]
do
echo $1
shift # schiebt die Parameter eine Position weiter
done
Interessant ist es in diesem Zusammenhang, wenn das Skript mit einer Dateimaske aufgerufen wird. Es werden daraufhin alle Dateien angezeigt, die auf die Maske passen. Das verdeutlicht, dass nicht das Programm oder das Skript die Auflösung der Dateimaske übernimmt. Stattdessen wertet die Shell die Maske aus und übergibt dem Programm alle Dateinamen, die darauf passen. Das Skript muss sich also nicht darum kümmern.
Die Schleife, die alle Parameter durchläuft, wollen wir noch um eine Fallunterscheidung erweitern. Das Skript soll prüfen, ob der Parameter eine Option oder ein Argument ist. Eine Option beginnt immer mit einem Minuszeichen. Sie beschreibt, wie ein Befehl auszuführen ist. Alles andere ist ein Argument, also ein Objekt, auf das der Befehl angewendet werden soll. Als Drittes soll das Skript aber auf das Schlüsselwort »stop« reagieren. In diesem Fall soll das Skript beendet werden.
Die Fallunterscheidung wird mit dem Befehl case
eingeleitet. Es folgen die auszuwertende Variable und das Schlüsselwort in
. Anschließend werden die einzelnen Fälle aufgegliedert. Dabei können Wildcards wie der Stern verwendet werden, wie Sie dies von einer Dateimaske kennen. Hier steht ein Stern als Platzhalter für eine beliebige Zeichenfolge. Eine runde schließende Klammer schließt das Suchmuster. Es folgen die Befehle, die bei einem Treffer ausgeführt werden sollen. Jeder Fall wird durch ein doppeltes Semikolon abgeschlossen.
while [ $1 ]
do
case $1 in
stop) echo "Halt!" ; return 1 ;;
-*) echo "Option: $1" ;;
*) echo "Argument: $1" ;;
esac
shift # schiebt die Parameter eine Position weiter
done
Der Befehl return
beendet das Skript und liefert eine Fehlernummer, die Sie frei wählen können. Würde dort break
stehen, würde die Schleife verlassen und das Skript nach dem Befehl done
fortgesetzt werden. Alternativ könnte dort auch exit
stehen, was die gesamte Shell beenden würde.
Solche Fallunterscheidungen werden Sie bei den Startskripten des Systems wiederfinden. Diese erhalten die Kommandos start
, stop
oder restart
und werden entsprechend ihre Dienste daraufhin in Betrieb nehmen oder beenden.
Die Ausgabe aus Skripten kann einfach über den Befehl echo
erfolgen. Für größere Ausgabetexte kann das allerdings umständlich werden. Hier kann der Befehl cat
(siehe Abschnitt 1) verwendet werden, um Zeilen aus dem Skript auszugeben.
cat «!
cat liest das erste Zeichen nach dem doppelten
Kleinerzeichen und merkt es sich als Trennzeichen.
Der darauffolgende Text wird ausgegeben, bis
genau dieses Zeichen als einziges
Zeichen in einer eigenen Zeile erscheint.
!
echo "Danach geht das Skript normal weiter."
Wenn Sie mit dem Anwender Ihrer Skripte interagieren möchten, können Sie mit dem Befehl read
den Ablauf stoppen, und der Benutzer kann eine Zeile eingeben. Diese Zeile landet in der Variablen, die der Befehl read
als Parameter bekommen hat.
echo "Gib mal was ein!"
read EINGABE
echo $EINGABE