AA_PO-20949-123RF-Ivan_Mikhaylov_123RF-Muscheln.jpg

© Ivan Mikhaylov, 123RF

Programme in der Shell

Bash-Skripte sind Programme

09.07.2013
Wer regelmäßig mit der Shell arbeitet, wird leicht zum Programmierer: Schreiben Sie mehrere Shell-Befehle in eine Textdatei und machen diese ausführbar, haben Sie schon Ihr erstes Shell-Skript entwickelt. Die Shell bietet als Programmiersprache aber noch viel mehr.

Viele Shell-Skripte enthalten einfach nur diverse Befehle, die nacheinander laufen sollen. Ein Beispiel dafür ist das Skript in Listing 1: Rufen Sie es nach dem Anstöpseln einer Digitalkamera (oder dem Einstecken einer Speicherkarte) auf, erzeugt es automatisch einen neuen Unterordner mit einem Namen der Form 2013-06-30 und verschiebt alle Bilder von der Karte in diesen Ordner.

Listing 1

Bilder kopieren

#!/bin/bash
CAMERA=/media/camera/DCIM/100XYZAB
ZIEL=$HOME/Pictures/Import
DATE=$( date +%Y-%m-%d )
mkdir $ZIEL/$DATE
mv -v $CAMERA/* $ZIEL/$DATE/
sync
echo Bilder kopiert.

Schauen wir uns das Listing Zeile für Zeile an:

#!/bin/bash

Die erste Zeile enthält gar keinen Befehl, sondern einen Kommentar; hinter #! steht der Pfad zur Shell (Bash), und damit sagen Sie dem System, dass die Bash das Skript ausführen soll. Diese Zeile sollten Sie an den Anfang jeder Skriptdatei stellen.

CAMERA=/media/camera/DCIM/100XYZAB

Die folgende Zeile definiert eine Variable CAMERA und weist ihr den Wert /media/camera/DCIM zu. Wenn Sie das Skript mit Ihrer eigenen Kamera ausprobieren möchten, müssen Sie diesen Pfad anpassen. Nach dem Anschließen der Kamera wird diese eingebunden. Unterhalb von /media/ finden Sie dann einen neuen Ordner, der meist eine Kurzbezeichnung Ihres Kameramodells oder der Herstellers als Namen trägt. Auf der obersten Ebene des Kameradateisystems gibt es einen Ordner DCIM/, der ein weiteres Unterverzeichnis enthält – im Beispiel 100XYZAB; bei Ihnen wird es anders heißen. Setzen Sie den korrekten Pfad in der obigen Zeile ein, wenn Sie das Skript testen möchten.

ZIEL=$HOME/Pictures/Import

Ähnlich wie CAMERA den Ort speichert, in dem die Bilder auf der Kamera liegen, ist auch ZIEL eine Variable, in die Sie das Verzeichnis eintragen, in welches das Skript die Bilder kopieren soll. In diesem Beispiel gehen wir davon aus, dass es in Ihrem Home-Verzeichnis (das sich über $HOME ansprechen lässt) einen Ordner Pictures gibt – und darin einen Unterordner Import. Falls das nicht der Fall ist, ist es auch nicht tragisch, denn der übernächste Befehl wird diese Verzeichnisse erzeugen.

DATE=$( date +%Y-%m-%d )

Dieses Kommando ist komplexer. Schauen wir zunächst in die Klammern: Dort steht das Kommando date +%Y-%m-%d. Ohne Argumente gibt date das aktuelle Datum und die Uhrzeit aus. Mit den Angaben hinter dem Pluszeichen definieren Sie ein Format – %Y-%m-%d sorgt dafür, dass die Ausgabe im Format 2011-09-30 erfolgt. Würden Sie also in der Shell direkt date +%Y-%m-%d eingeben, würde das Tool das aktuelle Datum im Format 2011-09-30 ausgeben. Die Konstruktion $( ... ) nimmt nun die Ausgabe dieses Befehls und macht daraus ein Argument. Am Anfang steht ja noch der Zuweisungsbefehl DATE=. Das gesamte Kommando schreibt also in die Variable DATE das aktuelle Datum.

mkdir -p $ZIEL/$DATE

Jetzt wird ein neuer Ordner erstellt – der Pfad setzt sich aus $ZIEL, dem Verzeichnistrenner / und $DATE zusammen. Beachten Sie hier, dass Sie beim Setzen einer Variable den Namen ohne Dollarzeichen benutzen, beim Zugriff auf den Inhalt hingegen ein Dollarzeichen voranstellen müssen. Wenn $HOME z. B. den Wert /home/user hat, ergibt sich über die Definitionen von ZIEL und DATE ein Befehl der Form mkdir -p /home/user/Pictures/Import/2011-09-30. Die Option -p für das mkdir-Kommando sorgt dafür, dass eventuell fehlende Verzeichnisse "auf dem Weg" (also .../Pictures/Import und .../Pictures) gleich mit erzeugt werden, so dass dieses Kommando nicht fehlschlagen kann.

mv -v $CAMERA/* $ZIEL/$DATE/

Das vorvorletzte Kommando verschiebt dann alle Dateien im Bilderordner der Kamera in den Bilderordner auf Ihrer Festplatte; die Option -v sorgt dafür, dass mv die Namen aller verschobenen Dateien auf der Konsole ausgibt, damit Sie den Fortschritt verfolgen können.

sync

Mit sync sorgen Sie schließlich dafür, dass Sie nach Ausführen des Skripts die Kamera einfach abstöpseln können, ohne Datenverlust zu riskieren.

echo Bilder kopiert.

Zum Schluss gibt es noch eine Statusmeldung. Damit Sie das Skript verwenden können, speichern Sie es (z. B. als copycam.sh in Ihrem Home-Verzeichnis) und machen es mit

chmod a+x copycam.sh

ausführbar. Dann können Sie es später mit

~/copycam.sh

aufrufen. Wenn Sie die Datei (mit Root-Rechten) in das Verzeichnis /usr/local/bin/ kopieren, können Sie die Pfadangabe auch weglassen, also in der Shell einfach copycam.sh eingeben.

Wenn Sie ein Shell-Skript im Editor bearbeiten, kommen Sie übrigens in der Regel in den Genuss von Syntax Highlighting, der Editor hebt Schlüsselwörter, Klammern und Variablennamen farbig hervor. Das funktioniert z. B. in KDEs Editor Kate (Abbildung 1) und im Konsolen-Editor vi.

Abbildung 1: KDEs Editor Kate erkennt Shell-Skripte und aktiviert beim Bearbeiten das Syntax Highlighting.

Bash als Programmiersprache

Richtige Programmiersprachen können aber mehr, als nur einfache Befehlssequenzen abzuarbeiten. In der Einführung ins Programmieren ab Seite 38 haben Sie Fallunterscheidungen (If-Then-Else-Konstruktionen), Schleifen (mit dem Spezialfall der Zählschleife) sowie Prozeduren bzw. Funktionen kennengelernt. All das gibt es auch in der Shell.

Fallunterscheidungen haben in der Shell die folgende Syntax:

if Bedingung
then Befehle
else Befehle
fi

Die Befehlsworte sind also if, then, else und fi (eine rückwärts geschriebene Form von if, die das Ende der Fallunterscheidung anzeigt). Der Else-Fall muss nicht vorhanden sein. Das folgende Beispielskript exist prüft, ob eine Datei existiert:

#!/bin/bash
if test "$1" = ""
then echo "$0: mit Dateinamen aufrufen!"; exit
fi
if test -e $1
then echo $1 existiert
else echo $1 existiert nicht
fi

Der erste Test prüft mit dem in die Shell eingebauten Kommando test, ob das Skript überhaupt mit einem Argument aufgerufen wurde ($1 ist das erste Argument; wenn es das nicht gibt, ist $1 ein leerer String). Ist kein Argument vorhanden, bricht das Programm mit exit ab. Das funktioniert, weil das Skript in einer "Sub-Shell", also einem eigenen Shell-Prozess läuft.

Zum "Then-Fall" gehören also zwei Kommandos, die hier mit einem Semikolon voneinander getrennt sind – Sie können sie alternativ auch in mehrere Zeilen des Programms schreiben. Oft stehen auch then und else alleine in jeweils einer Zeile, und die Kommandos für die beiden Fälle folgen darunter. Eine üblichere (wenn auch weniger kompakte Schreibweise) der ersten Fallunterscheidung ist damit die folgende:

if test "$1" = ""
then
  echo "$0: mit Dateinamen aufrufen!"
  exit
fi

Ein direkter Vergleich mit if "$1" = "" ist übrigens nicht möglich: Die Bedingung in einer Fallunterscheidung muss immer ein Kommando sein, dessen Rückgabewert die Shell dann prüft. Zum Beispiel gibt es die beiden Programme true und false, die beide keine sichtbare Funktion haben, aber die Rückgabewerte 0 und 1 produzieren:

$ true; echo $?
0
$ false; echo $?
1
$ if true; then echo Ja; else echo Nein; fi
Ja

Zurück zum exist-Skript: Der zweite Test ruft test -e $1 auf. Wenn Sie in die Dokumentation zu test schauen (das geht mit help test, weil test ein Shell-Builtin, also ein fest in die Shell eingebautes Kommando ist), sehen Sie, dass das Kommando über -e dateiname prüft, ob eine bestimmte Datei vorhanden ist.

Um eine Syntax zu erlauben, die stärker an normale Programmiersprachen erinnert, können Sie statt des Schlüsselworts test auch öffnende und schließende eckige Klammern verwenden, z. B.

if [ "$1" = "" ]

Beachten Sie dabei, dass die Klammern zu den übrigen Komponenten der Zeile mit einem Leerzeichen Abstand halten müssen; der Ausdruck

if ["$1" = ""]

enthält also zwei Syntaxfehler. Für andere Shells als die Bash gibt es sogar ein Programm /bin/[, welches genauso arbeitet. Sie könnten damit auch den merkwürdigen Ausdruck

if /bin/[ "$1" = "" ]

verwenden, wenn Sie die Leser Ihrer Skripte irritieren möchten. Kombinieren Sie die Klammerschreibweise und das Aufteilen auf mehrere Zeilen, entsteht hübscher Code, der intuitiv zu verstehen ist:

if [ "$1" = "" ]
then
  echo "$0: mit Dateinamen aufrufen!"
  exit
fi

(Haben Sie $0 bemerkt? Darüber können Sie den Namen der Skript-Datei herausfinden.) Wollen Sie mehr als zwei Fälle prüfen, können Sie ein weiteres Schlüsselwort verwenden: elif steht für "else if", ein Beispiel:

if [ "$1" = "a" ]
then echo "a"
elif [ "$1" = "b" ]
then echo "b"
elif [ "$1" = "c" ]
then echo "c"
else echo "Weder a noch b noch c"
fi

Case-Befehl

Für mehrere Tests, die alle den Inhalt derselben Variable prüfen, können Sie auch den case-Befehl nutzen, der diese Aktionen übersichtlicher macht:

case "$1" in
  "a")
    echo "a" ;;
  "b")
    echo "b" ;;
  *)
    echo "Weder a noch b" ;;
esac

Hinter jedem Fall steht eine schließende Klammer; dann folgen einer oder mehrere Befehle, und der Code für einen Fall muss mit doppelten Semikola (;;) enden. Der gesamte case-Ausdruck endet mit esac, was wieder (wie bei iffi eine Rückwärtsschreibung von case ist.

For-Schleife

Die Bash kennt keine klassische Zählschleife, die eine Variable von einem Anfangs- zu einem Endwert hochzählt. Stattdessen gibt es etwas, das in anderen Sprachen als For-Each-Schleife bezeichnet wird. So können Sie z. B. schreiben:

for entry in *
do
  if [ ! -e $entry ]
  then echo "$entry gibt es nicht"
    continue
  fi
  if [ -f $entry ]
  then echo "Datei $entry"
  fi
  if [ -d $entry ]
  then echo "Verzeichnis $entry"
  fi
done

Das Sternchen ist das normale Wildcard-Zeichen; in der Schleife nimmt die Variable $entry nacheinander alle Namen von Dateien und Verzeichnissen im aktuellen Ordner an.

Die For-Schleife beenden Sie mit done. Neu in diesem Beispiel ist auch das Ausrufezeichen: Es kehrt den Wahrheitswert eines Tests um. Einen Test auf Ungleichheit zweier Variablen können Sie also z. B. als [ ! "$x" = "$y" ] schreiben – allerdings gibt es dafür auch die lesbarere Form [ "$x" != "$y" ].

Ein Klassiker der Bash-Skripte (auch hier in EasyLinux) ist das Konvertieren von Bildern, etwa in zusätzliche Vorschaubilder. Wollen Sie z. B. eine Fotogalerie erstellen, werden Sie meist auch kleine Versionen der Bilder benötigen. Sie können dazu einfach einen Vierzeiler (minipics.sh) schreiben:

#!/bin/bash
for Bild in *.jpg; do
  Mininame=$( basename $Bild .jpg )_s.jpg
  echo Erzeuge Vorschaubild für $Bild
  convert "$Bild" -resize 100x100 "$Mininame"
done

In der Schleife erhält die Variable Bild nacheinander die Namen aller JPG-Dateien (mit Endung .jpg und führt für jede dieser Dateien die Befehle im Inneren der Schleife aus: Zunächst wird der Name für die Vorschaudatei bestimmt: Hier nutzt das Skript die $(...)$-Konstruktion, die Sie bereits im ersten Skript gesehen haben. Innerhalb der Klammern entfernt der Befehl basename $Bild .jpg vom Ursprungsdateinamen die Endung (aus foto1.jpg wird also foto1), an das Ergebnis wird _s.jpg angehängt (so dass im Beispiel foto1_s.jpg entsteht. Diesen Namen speichert der Befehl in der Variablen Mininame.

Schließlich erledigt das Tool convert (aus dem ImageMagick-Paket, das Sie eventuell nachinstallieren müssen) die Umwandlung. Es verwendet die Variablen Bild und Mininame, in denen die Namen der Quell- und Zieldateien stehen, und sorgt über die Option -resize 100x100 dafür, dass die neue Datei Abmessungen hat, die 100 x 100 Pixel nicht überschreiten; die Seitenverhältnisse bleiben dabei erhalten: Ein Bild der Größe 600 x 400 Pixel wird darum auf 100 x 67 Pixel verkleinert. Abbildung 2 zeigt, wie das Skript arbeitet.

Abbildung 2: Das Skript "minipics.sh" erzeugt maximal 100 x 100 Pixel große Vorschaubilder.

Zählschleife

Über einen kleinen Trick können Sie auch eine Zählschleife programmieren. Zwar gibt es keine spezielle Syntax für das Hochzählen, aber dafür können Sie das Programm seq verwenden, das eine Sequenz aus Zahlen generiert:

$ seq 5 8
5
6
7
8

Über die schon zweimal gesehene $(...)-Konstruktion können Sie diese Zahlenwerte in einen anderen Befehl integrieren. Um in einer Variablen $i von 1 bis 100 zu zählen, schreiben Sie:

for i in $(seq 1 100)
do
  ...
done

Das Programm seq können Sie auch mit drei Argumenten aufrufen, das mittlere steht dann für die Veränderung, die in jedem Schritt stattfindet – normal wird immer 1 addiert. Dieser mittlere Wert darf auch negativ sein, was aber voraussetzt, dass der Anfangswert größer als der Endwert ist:

$ echo $( seq 20 -3 0 )
20 17 14 11 8 5 2

Wie Sie an diesem Beispiel sehen, wird der Endwert eventuell nicht erreicht; nach 2 wäre der nächste Wert -1, der ist aber zu klein, darum taucht er nicht mehr in der Ausgabe auf.

While-Schleife

Als letzte Schleife erwähnen wir die While-Schleife, sie hat die Syntax

while Bedingung
doBefehle
done

und führt die Befehle im Inneren aus, solange die Bedingung erfüllt ist – sie prüft dabei immer am Anfang. Sie können leicht ein interaktives Skript erstellen, das Eingaben von der Tastatur liest und abbricht, wenn Sie z. B. exit eingeben:

read x
while [ "$x" != "exit" ]
do
  echo Eingabe war $x
  read x
done

Es verwendet das Kommando read, das einen oder mehrere Variablennamen als Argumente akzeptiert. Geben Sie (wie oben) nur einen Namen an, landet die ganze eingegebene Zeile in der Variable. Wenn Sie mehrere Variablen angeben, zerlegt read Ihre Eingabe in Teile und weist diese den Variablen zu. (Trennzeichen sind das Leer- und das Tabulatorzeichen.) Wenn es in diesem Fall mehr Teile als Variablen gibt, landet der Rest in der letzten Variable.

Funktionen

Zum Abschluss stellen wir noch Shell-interne Funktionen vor: Diese können Sie einfach in einer C-ähnlichen Syntax definieren und dann direkt in der Shell oder in einem Skript verwenden.

Anders als in normalen Programmiersprachen erhalten Funktionsargumente keine Namen, sondern werden über ihre Aufrufposition angesprochen; das funktioniert wie beim Aufruf eines Shell-Skripts:

function addiere {
  echo Berechne $1 plus $2
  ergebnis=$(( $1 + $2 ))
  echo Summe ist $ergebnis
}

Die Funktion können Sie mit zwei Argumenten aufrufen, z. B. addiere 5 20, und Sie erhalten dann die Ausgabe

Berechne 5 plus 20
Summe ist 25

Bei der Gelegenheit haben Sie auch gleich gesehen, wie Sie in der Shell rechnen können; Sie packen dazu den mathematischen Ausdruck einfach in $((...)). Es gibt hier nur Integer-Werte, Sie können also keine Zahlen mit Nachkommastellen verwenden. Auch bei der Berechnung von Brüchen (mit /) entstehen immer ganze Werte, die Shell führt eine Division mit Rest durch:

$ echo $(( 5/3 )), $(( 6/3 ))
1, 2

Hilfe im Netz

Um das Rad nicht mehrfach neu zu erfinden, können Sie im Internet auf Suche nach Skripten für bestimmte Aufgaben gehen: Viele Anwender veröffentlichen ihre Skript-Lösungen, und es fördert das Verständnis, wenn Sie ein solches Skript herunterladen und nachvollziehen, was es tut. Eine Sammlung von Skripten und Links zu anderen Webseiten über Skripte finden Sie auf der Shelldorado-Seite [1] (Abbildung 3).

Abbildung 3: Die Webseite "shelldorado.com" bietet unter anderem eine umfassende Linksammlung zu Shell-Skripten.

Darüber hinaus hat der Verlag Galileo Computing eine vollständige HTML-Version des Buchs "Shell-Programmierung" von Jürgen Wolf ins Netz gestellt [2], die gedruckte Version hat 780 Seiten, so dass ausreichend Lektüre für Ihre Ausbildung zum Shell-Programmierer vorhanden ist.

Infos

[1] Shelldorado: http://www.shelldorado.com/

[2] Jürgen Wolf, "Shell-Programmierung. Einführung, Praxis, Referenz", http://openbook.galileocomputing.de/shell_programmierung/

Diesen Artikel als PDF kaufen

Express-Kauf als PDF

Umfang: 4 Heftseiten

Preis € 0,99
(inkl. 19% MwSt.)

LinuxCommunity kaufen

Einzelne Ausgabe
 
Abonnements
 

Ähnliche Artikel

Kommentare