Shell programmieren mit Bash Scripts unter Linux

Linux-Kommandos an sich sind schon sehr mächtig. Sie erledigen viele Aufgaben im Handumdrehen, für die Sie unter anderen Betriebssystemen weitaus mehr Zeit bräuchten. Doch so richtig entfaltet Linux seine Kraft erst, wenn Sie Routine-Arbeiten automatisch erledigen lassen.

Routine-Arbeiten unter Linux gibt es eine Menge – besonders wenn das System als Server arbeitet. Allen voran steht ein vernünftiges Backup der Daten. Und das sollte möglichst einfach und schnell gehen. Doch bevor es ans Backup geht, sollten Sie ein paar Grundlagen kennen.

Grundlagen Bash-Skript

Das Werkzeug erster Wahl für Routine-Aufgaben ist die gute alte Bash. Die haben Sie schon kennen gelernt als Befehlsempfänger für Ihre Kommandozeilen-Eingaben. Und natürlich kann die Bash auch mehrere Befehle nacheinander aufnehmen, die Sie in eine Datei schreiben. Auf diese Weise lassen sich einfache Aufgaben lösen, wie das Kopieren einer Datei in zwei unterschiedliche Ordner. Dazu öffnen Sie einen beliebigen Text-Editor — auf der Shell verwenden Sie zum Beispiel pico und schreiben Sie ein Script wie dieses

#!/bin/bash

cp ~/datei.ext ~/test1/

cp ~/datei.ext ~/test2/

Die erste Zeile enthält einen Verweis auf das Programm, welches das Skript ausführen soll. In diesem Fall also wird Bash gestartet und führt die angegebenen Befehle aus. Aus dem Heimatverzeichnis des Benutzers wird die Datei datei.ext einmal in das Verzeichnis ~/test1/ und einmal in ~/test2/ kopiert. Zwei Arbeiten sind also zu einer zusammen gefasst worden. Die Tilde ~ steht in Linux immer für das Heimatverzeichnis eines Benutzers. test1 und test2 sind also Unterverzeichnisse des Heimatverzeichnisses.

Das Skript starten

Wenn Sie möchten, schreiben Sie das kleine Skript ab und speichern Sie es unter dem Namen cptwice.sh in Ihrem Heimatverzeichnis. Um es zu starten, müssen Sie es zunächst ausführbar machen. Das geht auf der Shell mit

chmod u+x cptwice.sh

Danach legen Sie die Verzeichnisse „test1“ und „test2“ in Ihrem Heimatverzeichnis an und erzeugen mit

touch ~/datei.ext

die Testdatei. Sie ist leer – aber das macht in diesem Fall nichts.

Danach starten Sie das Skript von Ihrem Heimatverzeichnis aus mit

./cptwice.sh

Meldungen werden keine ausgegeben. Wenn Sie aber selbst mit der Anweisung

ls -R test*

in die beiden Unterverzeichnisse sehen, werden Sie dort die Dateien entdecken.

Für die weiteren Tests sollten Sie die Files aber gleich wieder löschen.

keine Beschreibung

If-Konstruktionen

Das bisschen Kopieren ist noch lange nicht alles. Bash-Scripts verfügen über viele Merkmale, die auch richtige Programmiersprachen mit sich bringen. Dazu gehört die Verarbeitung von Bedingungen, die Verarbeitung von Variablen und die Fähigkeit, Schleifen zu bilden.

Ein Beispiel für Bedingungen

#!/bin/bash

if test -f datei.ext

then

    cp datei.ext ~/test1/

    cp datei.ext ~/test2/

else

    echo "Datei nicht vorhanden"

fi

Sie sehen wieder die schon bekannten Kopierbefehle. Diesmal allerdings soll zunächst überprüft werden, ob es die zu kopierende Datei überhaupt gibt. Das geschieht über den Befehl if. In der eckigen Klammer steht die Bedingung, die überprüft werden soll. Das test -f überprüft, ob es wirklich eine Datei namens datei.ext gibt.

Falls ja, kann es mit dem Kopieren los gehen. Falls nicht, erscheint eine Fehlermeldung. Übrigens können Sie den Test-Befehl abkürzen: Alternativ sieht die Zeile dann so aus:

if [ -f datei.ext ]; then

Das schafft ein wenig mehr Überblick in umfangreicheren Scripts. Welche Bedingungen „test“ sonst noch abfragen kann, erfahren Sie mit „man test“.

Variablen verwenden

Bislang funktioniert unser Skript schon ganz gut. Was aber, wenn Sie nicht mehr datei.ext kopieren wollen, sondern file.dat? Dann müssen Sie jede Zeile im Skript ändern.

Dieses Problem gehen Sie an, indem Sie eine Variable verwenden:

#!/bin/bash

FILENAME="datei.ext"

if test -f $FILENAME; then

    cp $FILENAME ~/test1/

    cp $FILENAME ~/test2/

else

    echo "Datei $FILENAME nicht vorhanden"

fi

In der zweiten Programmzeile sehen Sie, wie eine Variable ihren Wert erhält: FILENAME ist der Platzhalter für datei.ext. Im weitern Verlauf des Programms wird statt $FILENAME immer datei.ext eingefügt.

Sobald Sie hinter FILENAME= einen anderen Dateinamen schreiben und das Programm wieder starten, wird diese Datei in die beiden Unterverzeichnisse kopiert.

Achten Sie bei den Variablen genau auf die Schreibweise: Wird einer Variablen ein Wert zugewiesen, so schreibt man die Variable ohne vorangestelltes Dollar-Zeichen, also

WERT="Hallo"

Soll hingegen der Wert einer Variablen eingefügt werden, so steht davor ein Dollarzeichen, also

echo $WERT

Schleifen programmieren

Sie möchten gleich mehrere Dateien verarbeiten? Kein Problem. Das geht ganz einfach mit einer Schleife. Probieren Sie es zunächst einmal so:

#!/bin/bash

FILELIST="datei1.ext datei2.ext datei3.ext"

for FILENAME in $FILELIST

do

  if test -f $FILENAME; then

    cp $FILENAME ~/test1/

    cp $FILENAME ~/test2/

  else

    echo "Datei $FILENAME nicht vorhanden"

  fi

done

Zunächst haben Sie einfach ein paar Dateinamen in eine Variable geschrieben. Der Befehl for erkennt dies anhand der enthaltenen Leerzeichen als Liste und geht sie der Reihe nach durch. Jeder Listeneintrag wird der Variablen FILENAME zugewiesen. Danach wird die Datei kopiert, die gerade an der Reihe ist. Beachten Sie, dass nach einem for auch ein do stehen muss und dass ans Ende der Schleife ein done gehört.

Und die Fehlermeldung wurde auch noch ein wenig ergänzt. Nun gibt sie an, welche Datei nicht vorhanden ist.

Intelligentes Skript

Noch ist unser Skript nicht sehr eigenständig. Denn was bringt es, wenn man die Dateinamen alle in die Liste selbst hineinschreiben muss. Besser wäre es doch, wenn Sie die Liste als Kommandozeilenparameter übergeben könnten, also in der Form:

./cptwice.sh datei1.ext datei2.ext

und so weiter.

Das geht auch ganz einfach. Bauen Sie dazu das Script wie folgt um:

#!/bin/bash

if [ $# -lt 1 ]; then

  echo "Fehler: Kein Dateiname eingegeben"

  echo "Benutzung: $0 datei.ext [datei2.ext] [datei.ext]"

fi

while [ $# -gt 0 ]

do

  FILENAME=$1

  if test -f $FILENAME; then

    echo "Kopiere $FILENAME"

    cp $FILENAME ~/test1/

    cp $FILENAME ~/test2/

  else

    echo "Datei $FILENAME nicht vorhanden"

  fi

  shift

done

Jetzt versteht unser Programm auch Kommandozeilenparameter. Zunächst allerdings prüft es, ob die Parameter vorhanden sind. Das geht am besten mit der Spezialvariablen $#. Die enthält die Anzahl der übergebenen Parameter. Mit

if [ $# -lt 1 ]; then

prüft das Skript, ob weniger als ein Parameter vorhanden sind. Ist das der Fall, gibt das Skript eine Fehlermeldung aus und eine kurze Anleitung über die Benutzung. Dabei kommt $0 zum Einsatz. Diese Variable enthält den Programmnamen selbst.

Sind genügend Parameter vorhanden, dann tritt eine While-Schleife in Kraft. Sie zählt bei jedem Durchlauf die Anzahl der vorhandenen Parameter mit

while [ $# -gt 0 ]

Das -gt steht für “greater than”, also “größer als”. So lange diese Bedingung zutrifft, wird in der folgenden Schleife immer der Parameter $1 der Variablen FILENAME zugewiesen. Kurz vor dem Ende der Schleife entdecken Sie noch einen ganz wichtigen Befehl: „shift“ sorgt dafür, dass der erste Eintrag aus der Parameter-Liste gelöscht wird und dafür der nächste an seine Stelle trifft. So rücken also alle Parameter auf die Stelle von $1 und können so prima abgearbeitet werden.

Noch intelligenter?

Soll das Script noch ein wenig schlauer werden und zum Beispiel auch Aufrufe wie

cptwice.sh *.ext filezz.dat *.sh

verstehen und folglich alle Dateien mit der Endung .ext und der Endung .sh sowie die einzelne Datei filezz.dat verarbeiten? Dann müssen Sie das Skript nur ein klein wenig umschreiben. Los geht’s bei der while-Schleife – den oberen Teil des Programms lassen Sie wie er ist:

while [ $# -gt 0 ]

do

  for FILENAME in $1

  do

    if test -f $FILENAME; then

      echo "Kopiere $FILENAME"

      cp $FILENAME ~/test1/

      cp $FILENAME ~/test2/

    else

        echo "Datei $FILENAME nicht vorhanden"

    fi

  done

  shift

done

Entscheidend ist die Schleife, die mit

for FILENAME in $1

beginnt. Statt einfach den Dateinamen aus dem Parameter zu übernehmen, untersucht das Skript jetzt das aktuelle Verzeichnis auf Dateien, die zum übergebenen Muster passen. Steht also *.ext im Parameter, so nimmt FILENAME nacheinander alle Dateinamen an, die auf .ext enden. Steht hingegen nur ein Dateiname im Parameter, so nimmt FILENAME eben diesen an und die Schleife ist auch gleich danach beendet.

Nachvollziehen lässt sich dieses Verhalten mit dem Befehl ls. Schreiben Sie einmal

ls *.ext

und es werden alle Dateien mit dieser Endung gezeigt. Probieren Sie es dagegen mit

ls filezz.dat

so wird nur diese Datei angezeigt, sofern sie vorhanden ist.

Externe Programme nutzen

Bislang haben Sie nur interne Fähigkeiten der Bash-Scripts kennen gelernt. Noch raffinierter wird es aber, wenn Sie auch noch externe Programme in Ihre Scripts einbinden. Bauen Sie Ihr Skript doch in eine kleine Backup-Maschine um. Die packt die ihr übergebenen Daten in ein Archiv und schreibt das aktuelle Datum mit in den Archiv-Namen hinein.

Dazu brauchen Sie folgende Zutaten:

date zum ermitteln des Datums

tar zum Erzeugen einer Archivdatei

gz zum komprimieren der Archivdatei

In einem Skript sieht das Ganze dann so aus:

#!/bin/bash

# Einfaches Backup-Script

SIKFILEPRE=${HOME}/sicherung

SIKFILENAME=${SIKFILEPRE}_`date +%Y%m%d`.tar

if [ $# -lt 1 ]; then

    echo "Fehler: Kein Dateiname eingegeben"

    echo "Benutzung: $0 datei.ext [datei2.ext] [datei.ext]"

    exit 1

else

    echo ${SIKFILENAME} wird erzeugt

    tar -cvf ${SIKFILENAME} "$@"

    if [ $? -eq 0 ]

    then

        echo Dateien erfolgreich gesichert

    else

        echo Fehler bei Datensicherung

        exit 2

    fi

    gzip $SIKFILENAME

fi

Los geht es mit der Zeile

SIKFILEPRE=${HOME}/sicherung

Hier erscheint zum ersten Mal die Variable HOME. In ihr ist der Name des Heimatverzeichnisses eines User gespeichert. Ungewöhnlich sind die geschweiften Klammern um den Variablennamen. Sie dienen dazu, Anfang und Ende des Namens zu markieren. Denn ohne die Klammern würde die Bash in der nächsten Zeile

SIKFILENAME=${SIKFILEPRE}_`date +%Y%m%d`.tar

einen Fehler verursachen. Denn stünde dort

SIKFILENAME=$SIKFILEPRE_`date +%Y%m%d`.tar

so würde die Bash nach der Variablen $SIKFILEPRE_ suchen — und die ist leer. In umfangreicheren Skripten mit mehreren Variablen sollten Sie sich die Schreibweise mit den geschweiften Klammern angewöhnen – das hilft, eine Menge Fehler zu vermeiden.

In der Zeile, die SIKFILENAME erzeugt, findet sich noch eine weitere Spezialität. Hier wird nämlich in den Dateinamen noch das aktuelle Datum eingebaut, so dass die Datei zum Beispiel sicherung20041204 heißt. Um das Datum zu erzeugen verwenden Sie den Befehl date. Probieren Sie ihn einmal aus und geben Sie auf der Shell

date +%Y%m%d

ein. Sie bekommen eine Anzeige wie 20041210, also den 10.12.2004. Damit das Skript allerdings diese Datumsangabe übernehmen kann, muss der date-Befehl in so genannten Backticks eingebaut werden. Die erzeugen Sie, indem Sie bei gedrückter Umschalt-Taste auf die Akzent-Taste links neben dem Backslash klicken. Achtung: Verwechseln Sie das nicht mit dem normalen, einfachen Anführungszeichen.

Die Zeile setzt aus der Variablen SIKFILEPRE und der Datumsangabe einen Dateinamen zusammen, unter dem später die Datei gesichert wird. In dieser Auslegung ist das Skript für tägliche Sicherungen vorgesehen. Wenn Sie stündlich sichern wollen, brauchen Sie einen anderen Dateinamen, der auch noch die Stunde berücksichtigt. Das geht mit

date +%Y%m%d%k

Dann hängt das Programm auch noch die aktuelle Stunde mit an.

Ist der Name erzeugt, geht es an die Datensicherung. Zunächst wird wie gehabt geprüft, ob Parameter eingegeben wurden. Neu ist die Befehlszeile exit 1. Das Programm wird, falls keine Parameter vorhanden sind, mit dem Fehlercode 1 abgebrochen. Das ist sinnvoll, falls Sie das Skript von einem anderen aus aufrufen und prüfen möchten, ob es korrekt ausgeführt wurde.

Falls alles in Ordnung ist, wird die Sicherungsdatei erzeugt. Dazu übergibt das Skript einfach die komplette Parameterliste an das Backup-Programm Tar. Dieses erzeugt mit -cvf eine neue Sicherungsdatei unter dem zuvor festgelegten Namen und erhält die Parameter in $@. Die Anführungszeichen sind notwendig, falls dem Sicherungsskript per Wildcard auch Dateinamen übergeben werden, die Leerzeichen enthalten. Das passiert zum Beispiel dann, wenn im Verzeichnis eine dummy file.ext vorhanden ist und das Skript mit dem Parameter *.ext aufgerufen wird.

Nach dem Tar überprüft der Befehl

if [ $? -eq 0 ]

ob das Programm erfolgreich abgeschlossen wurde. Falls nicht erscheint noch einmal eine Fehlermeldung und das Skript bricht seinerseits mit einem Fehlercode ab.

Sind alle Vorarbeiten erledigt, komprimiert gzip die fertige TAR-Datei und speichert sie unter der Endung gz.

Übrigens: Wenn Sie einen Blick in die Datei werfen wollen, starten Sie einfach den Midnight-Commander, führen Sie den Cursor über die tar.gz-Datei und drücken Sie die Return-Taste. Damit öffnet der Commander das Backup-Archiv, als wäre es ein Verzeichnis.

Fallen für Bash-Programmierer

Bash-Programmierung ist nur etwas für starke Gemüter — zumindest so lange, bis man sich an die Eigenheiten der Skript-Sprache gewöhnt hat. Bis dahin lauern jede Menge Fallen auf den Programmierer, die er von anderen Sprachen so nicht gewohnt ist:

– Die Variablenfalle I: Bei der Zuweisung hat eine Variable kein vorangestelltes Dollarzeichen, also

WERT="Hallo"

Wird die Variable jedoch an anderer Stelle im Programm verwendet, so muss das Dollarzeichen vorangestellt werden, also

echo $WERT

– Die Variablenfalle II: Bei der Zuweisung einer Variablen darf vor und nach dem Gleichheitszeichen keinesfalls ein Leerzeichen stehen. Wer aus alter Programmierergewohnheit

WERT = "Hallo"

schreibt, hat schon verloren und produziert einen Fehler. Denn es muss so aussehen:

WERT="Hallo"

– Die Ausführbarkeitsfalle: Bevor Sie ein Skript ausführen können, muss es mit dem entsprechenden Flag versehen werden. Denn allein anhand der Endung .sh erkennt Linux nicht, dass es die Datei ausführen soll. Sie müssen zuerst:

chmod u+x script.sh

eingeben. Oder, falls Eigentümer und Gruppe das Programm ausführen sollen

chmod ug+x

– Die Verzeichnisfalle: Beim Starten eines Skripts aus dem eigenen Verzeichnis heraus passiert nichts. Warum? Weil Sie wahrscheinlich nur

skript.sh

eingegeben haben. Sie müssen statt dessen

./skript.sh

eingeben, wenn das Skript im selben Verzeichnis steckt.

– Die IF-Falle I: Vergessen Sie nicht, dass jedes „if“ in Bash-Skripts auch ein „then“ braucht. Und am Schluss des If-Konstrukts muss ein „fi“ stehen.

– Die IF-Falle II: Wenn Sie Test-Bedinungen in eckige Klammern schreiben, so muss zwischen Bedingung und Klammer immer ein Leerzeichen stehen, also

if [ $# -gt 0 ]

und nicht

if [$# -gt 0]

Nützliche Linux-Kommandos

“cut” schneidet Felder aus Texten heraus.

“df” ermittelt den gesamten, den belegten und den freien Platz der Festplatten. Allerdings sind die Angaben schwer zu entziffern in Byte. Probieren Sie es deshalb mit df -h. Das h steht für “human readable” und verwandelt die Byteangaben in Kilobyte, Megabyte und gegebenenfalls Gigabyte. Das erleichtert das Lesen.

“sed” ist der Streamline-Editor. Damit können Sie in beliebigen Texten beispielsweise Suchen und ersetzen. Ein typischer Aufruf ein einem Skript sieht so aus:

NEWNAME=`echo $NAME | sed ‘s/Ä/ae/g’`

Damit wird der Inhalt der Variablen NAME ausgegeben und an sed weiter geleitet. Das Programm ersetzt alle “Ä” im Text mit “ae”. Achten Sie darauf, dass der echo-Befehl in Backticks, also rückwärts gewandten Anführungszeichen, stehen muss.

“wc” zählt Wörter oder Zeichen in einem Text

“grep” filtert Ausgaben nach regulären Ausdrücken. “nl” nummeriert die Zeilen bei der Ausgabe einer Datei

“who” stellt fest, wer online ist

“uptime” zeigt an, wie lange Ihr System bereits läuft. Außerdem erhalten Sie Kennzahlen über die Systemauslastung.

“wall” sendet eine Meldung an alle angemeldeten Benutzer

“reboot” startet den Rechner sofort neu

“sort” gibt den Inhalt einer Datei sortiert auf dem Bildschirm aus

“halt” fährt den Rechner sofort herunter

Ähnliche Beiträge