12.8bash-Script-Beispiele

Shell-Programme sind einfache Textdateien mit einigen Linux- und/oder bash-Kommandos. Nach dem Start eines Shell-Programms werden diese Kommandos der Reihe nach ausgeführt. Dem Shell-Programm können Parameter wie einem normalen Kommando übergeben werden. Diese Parameter können innerhalb des Programms ausgewertet werden.

Da die einfache sequenzielle Ausführung einiger Kommandos wenig Spielraum für komplexe Aufgabenstellungen lässt, kennt die bash Kommandos zur Bildung von Verzweigungen, Schleifen und Funktionen. Damit steht Ihnen eine echte Programmiersprache zur Verfügung, für die Sie weder einen Compiler noch C-Kenntnisse benötigen.

Typische Anwendungen für Shell-Programme sind die Automatisierung von oft benötigten Kommandofolgen zur Installation von Programmen, zur Administration des Systems, zur Durchführung von Backups, zur Konfiguration und Ausführung einzelner Programme etc.

Die folgenden Seiten geben nur eine erste Einführung in die Programmierung mit der bash. Unzählige weitere Informationen und Beispiele finden Sie auf der ausgezeichneten Website http://bash-hackers.org.

Aus Geschwindigkeitsgründen kommt bei Ubuntu für die Ausführung von Scripts standardmäßig die dash statt der bash zum Einsatz:

user$ ls -l /bin/sh lrwxrwxrwx 1 root root ... /bin/sh -> dash

Die dash ist zwar in vielen Fällen effizienter als die bash, ist aber nicht zu 100 Prozent kompatibel. Wenn Sie möchten, dass Ihr Script mit der bash ausgeführt wird, müssen Sie in der ersten Zeile des Scripts statt /bin/sh explizit /bin/bash angeben:

#!/bin/bash

Unter Linux wimmelt es nur so von Beispielen für die bash-Programmierung, auch wenn Sie bisher möglicherweise nichts davon bemerkt haben. Viele Kommandos, die Sie während der Installation, Konfiguration und Administration von Linux ausführen, sind in Wirklichkeit bash-Programme.

Das folgende find/grep-Kommando durchsucht das Verzeichnis /etc/ nach shell-Programmen. Dabei werden alle Dateien erkannt, die als ausführbar gekennzeichnet sind und die die Zeichenkette \#! ... sh enthalten. Die Ausführung des Kommandos nimmt einige Zeit in Anspruch, weil das gesamte Dateisystem durchsucht wird.

user$ find /etc -type f -perm +111 -exec grep -q '#!.*sh' {} \; -print

Beispiel 1: grepall

Angenommen, Sie verwenden häufig die Kommandos grep und find, um im gerade aktuellen Verzeichnis und allen Unterverzeichnissen nach Dateien zu suchen, die eine bestimmte Zeichenkette enthalten. Das richtige Kommando sieht so aus:

user$ find . -type f -exec grep -q suchtext {} \; -print

Wenn Sie wie ich jedes Mal neu rätseln, welche Kombination der Optionen dazu erforderlich ist, liegt es nahe, das neue Kommando grepall zu definieren, das eben diese Aufgabe übernimmt. Dazu starten Sie Ihren Lieblingseditor, um die Textdatei grepall zu schreiben. Die Datei besteht aus nur zwei Zeilen, wobei die erste den Programmnamen des Interpreters angibt, der die Script-Datei ausführen soll.

#!/bin/sh find . -type f -exec grep -q $1 {} \; -print

Kleine Textdateien ohne Editor erstellen

Wenn Sie sich den Editoraufruf sparen möchten, können Sie die Datei auch mit cat erstellen: Geben Sie das Kommando cat > grepall ein. Das Kommando erwartet jetzt Daten aus der Standardeingabe (Tastatur) und schreibt diese in die Datei grepall. Geben Sie nun das Kommando mit all seinen Optionen ein. Anschließend beenden Sie cat mit (Strg)+(D) (das entspricht EOF, also end of file). Die resultierende Datei können Sie mit cat grepall ansehen.

Der Versuch, die gerade erstellte Datei grepall auszuführen, endet mit der Fehlermeldung permission denied. Der Grund für diese Meldung besteht darin, dass bei neuen Dateien generell die Zugriffsbits (x) zum Ausführen der Datei deaktiviert sind. Das können Sie aber rasch mit chmod ändern. grepall abc liefert jetzt die gewünschte Liste aller Dateien, die die Zeichenkette »abc« enthalten:

user$ ./grepall abc bash: ./grepall: Permission denied user$ chmod a+x grepall user$ ./grepall abc ./bashprg.tex

Damit Sie das Kommando grepall unabhängig vom aktuellen Verzeichnis einfach durch grepall (ohne vorangestelltes Verzeichnis) ausführen können, müssen Sie es in ein Verzeichnis kopieren, das in $PATH enthalten ist. Wenn das Kommando allen Benutzern zugänglich sein soll, bietet sich /usr/local/bin an:

root# cp grepall /usr/local/bin

Beispiel 2: stripcomments

Auch das zweite Beispiel ist ein Einzeiler. Sie übergeben an das Kommando stripcomments eine Textdatei. Die drei verschachtelten grep-Kommandos eliminieren nun alle Zeilen, die mit den Zeichen # oder ; beginnen bzw. ganz leer sind. Kommentare werden auch dann entfernt, wenn sich vor den Zeichen # oder ; Leer- oder Tabulatorzeichen befinden. Das Kommando eignet sich ausgezeichnet dazu, um bei Konfigurationsdateien alle Kommentarzeilen zu entfernen und nur die tatsächlich gültigen Einstellungen anzuzeigen.

#!/bin/sh grep -Ev '^[[:space:]]*#|^[[:space:]]*;|^$' $1

Kurz zur Erklärung: Das Muster ^[[:space:]]*\# findet Zeilen, die mit # beginnen, wobei zwischen dem Zeilenanfang (^) und # beliebig viele Leer- und Tabulatorzeichen sein dürfen. Analog erfasst der Ausdruck ^[[:space:]]*; alle Zeilen, die mit ; beginnen. Das dritte Muster gilt für leere Zeilen, die nur aus Zeilenanfang und Zeilenende ($) bestehen.

Die Option -v invertiert die übliche Funktion von grep: Statt die gefundenen Zeilen zu extrahieren, liefert grep nun alle Zeilen, auf die das Muster nicht zutrifft. Die Option -E aktiviert die erweiterte grep-Syntax, die die Kombination mehrerer Suchausdrücke mit dem Zeichen | erlaubt.

Beispiel 3: applysedfile

Die beiden obigen Beispiele zeigen zwar gut, wie Sie sich etwas Tipp- und Denkarbeit ersparen können, deuten die weitreichenden Möglichkeiten der Script-Programmierung aber noch nicht einmal an. Schon mehr bietet in dieser Hinsicht das nächste Beispiel: Nehmen Sie an, Sie stehen vor der Aufgabe, in einem ganzen Bündel von Dateien eine Reihe gleichartiger Suchen-und-Ersetzen-Läufe durchzuführen. Das kommt immer wieder vor, wenn Sie in einem über mehrere Dateien verteilten Programmcode einen Variablen- oder Prozedurnamen verändern möchten. Ich stand bei der Überarbeitung dieses Buchs für die fünfte Auflage aufgrund der neuen Rechtschreibung vor einem ähnlichen Problem: In Dutzenden von *.tex-Dateien sollte »daß« durch »dass«, »muß« durch »muss« etc. ersetzt werden.

Das Script-Programm applysedfile hilft bei derartigen Aufgaben. Der Aufruf dieses Scripts sieht folgendermaßen aus:

user$ applysedfile *.tex

Das Programm erstellt nun von allen *.tex-Dateien eine Sicherheitskopie *.bak. Anschließend wird das Unix-Kommando sed verwendet, um eine ganze Liste von Kommandos für jede *.tex-Datei auszuführen. Diese Kommandos müssen sich in der Datei ./sedfile befinden, die von applysedfile automatisch benutzt wird. Der Code von applysedfile sieht folgendermaßen aus:

#! /bin/bash # Beispiel applysedfile # Verwendung: applysedfile *.tex # wendet ./sedfile auf die Liste der übergebenen Dateien an for i in $* do echo "process $i" # make a backup of old file cp $i ${i%.*}.bak # build new file sed -f ./sedfile < ${i%.*}.bak > $i done

Kurz einige Anmerkungen zur Funktion dieses kleinen Programms: Bei den vier ersten Zeilen handelt es sich um Kommentare, die mit dem Zeichen # eingeleitet werden.

for leitet eine Schleife ein. Für jeden Schleifendurchgang wird ein Dateiname in die Variable i eingesetzt. Die Liste der Dateinamen stammt aus $*. Diese Zeichenkombination ist ein Platzhalter für alle an das Programm übergebenen Parameter und Dateinamen.

Der Schleifenkörper gibt den Namen jeder Datei aus. Mit cp wird eine Sicherungskopie der Datei erstellt. (Dabei werden zuerst alle Zeichen ab dem ersten Punkt im Dateinamen gelöscht. Anschließend wird .bak angehängt.) Schließlich wird das Kommando sed für die Datei ausgeführt, wobei die Steuerungsdatei sedfile aus dem lokalen Verzeichnis verwendet wird.

Für die Umstellung auf die neue Rechtschreibung sahen die ersten Zeilen dieser Datei wie folgt aus:

s.daß.dass.g s.muß.muss.g s.paßt.passt.g s.läßt.lässt.g

Dabei handelt es sich bei jeder Zeile um ein sed-Kommando, das die erste Zeichenkette durch die zweite ersetzt (Kommando s). Der nachgestellte Buchstabe g bedeutet, dass das Kommando auch mehrfach innerhalb einer Zeile ausgeführt werden soll (falls »daß« oder »muß« mehrere Male innerhalb einer Zeile auftreten sollte).

Beispiel 4: Backup-Script

Das folgende Script wird jede Nacht automatisch auf meinem root-Server ausgeführt. Als Erstes wird die Variable m initialisiert, die den aktuellen Monat als Zahl enthält. Das Kommando date liefert das aktuelle Datum samt Uhrzeit. Die Formatzeichenkette +%m extrahiert daraus den Monat.

Nun erstellt tar ein Backup des Verzeichnisses /var/www. Das Archiv wird nicht direkt in einer Datei gespeichert, sondern mittels | an das Kommando curl weitergeleitet. curl überträgt die Daten auf einen FTP-Server (Benutzername kofler, Passwort xxxx, IP-Adresse 1.2.3.4). Auf dem FTP-Server wird das Backup unter dem Namen www-monat.tgz gespeichert.

Auf diese Weise entstehen über den Verlauf eines Jahres monatliche Backup-Versionen, sodass ich zur Not auch einen alten Zustand meiner Website rekonstruieren kann, sollte das erforderlich sein. Gleichzeitig ist der Platzbedarf der Backup-Dateien gering. Zu jedem Zeitpunkt gibt es maximal 12 Versionen, also www-01.tgz bis www-12.tgz.

Das Kommando mysqldump erstellt ein Backup der MySQL-Datenbank cms, in der das Content-Management-System (CMS) meiner Website alle Seiten und unzählige andere Daten speichert. Abermals wird das Backup mittels | an curl weitergegeben und auf meinem FTP-Server gespeichert.

#!/bin/sh m=$(date "+%m") cd /var tar czf - www | curl -T - -u kofler:xxxx ftp://1.2.3.4/www-$m.tgz mysqldump -u cms -pxxxx cms | curl -T - -u kofler:xxxx ftp://1.2.3.4/cms-$m.sql

Das gesamte Script habe ich unter dem Dateinamen /etc/myscripts/backup gespeichert. Um den täglichen Aufruf kümmert sich Cron (siehe Abschnitt 14.6, »Prozesse automatisch starten (Cron)«). Die dazu passende Konfigurationsdatei /etc/cron.d/backup sieht so aus:

# jeden Sonntag um 3:15 15 3 * * 0 root /etc/myscripts/backup

Beispiel 5: Thumbnails erzeugen

Als »Thumbnails« werden verkleinerte Versionen von Bilddateien bezeichnet. Das folgende Script wird in der Form makethumbs *.jpg aufgerufen. Es erzeugt das Unterverzeichnis 400x400 und speichert dort verkleinerte Kopien der ursprünglichen Bilder. Die Maximalgröße der neuen Bilder beträgt 400×400 Pixel, wobei die Proportionen des Originalbilds erhalten bleiben. Bilder, die kleiner sind, bleiben unverändert und werden also nicht vergrößert.

Das Script wendet das convert-Kommando aus dem Paket Image Magick an.

Für die Verkleinerung ist die Option -resize verantwortlich. -size bewirkt lediglich eine schnellere Verarbeitung.

#!/bin/sh # Verwendung: makethumbs *.jpg if [ ! -d 400x400 ]; then # Unterverzeichnis erzeugen mkdir 400x400 fi for filename do # alle Dateien verarbeiten echo "processing $filename" convert -size 400x400 -resize 400x400 $filename 400x400/$filename done

Beispiel 6: Studenten-Accounts einrichten

Das folgende Script habe ich benötigt, als ich zu Beginn einer Linux-Lehrveranstaltung für alle Studenten und Studentinnen einen Account einrichten wollte. Ausgangspunkt war die Datei students.txt, die zeilenweise die Nachnamen der Studenten enthielt:

huber mueller schmidt ...

Nun hätte ich jeden Studenten einzeln einrichten können, entweder mit einer grafischen Benutzeroberfläche oder durch die manuelle Ausführung von useradd, passwd und chage. Stattdessen habe ich das folgende Mini-Script verfasst:

#!/bin/bash while read s; do pw=$s"-1234" useradd $s echo -e "$pw\n$pw" | passwd --stdin $s chage -d 0 -E 2015-12-31 $s done < studenten.txt

Das Script durchläuft in der for-Schleife die Namen aller Studenten. Der gerade aktuelle Name befindet sich in der Variablen s. Die Variable pw enthält das Startpasswort, das sich aus dem Namen plus -1234 zusammensetzt, also beispielsweise huber-1234. useradd richtet den Account ein. echo gibt zweimal das Passwort aus, getrennt durch ein Zeilenendezeichen. Diese Ausgabe wird freilich nicht angezeigt, sondern mit | an das passwd-Kommando weitergeleitet. Auf diese Weise wird das Passwort eingerichtet. (Das passwd-Kommando erwartet aus Sicherheitsgründen eine Wiederholung des Passworts. Deswegen muss $pw zweimal an passwd übergeben werden.)

chage -d 0 bewirkt, dass jeder Student unmittelbar nach dem ersten Login sein Passwort ändern muss. Die Option -E 2015-12-31 hat zur Folge, dass die Accounts Ende Dezember 2015 auslaufen und dann nicht mehr genutzt werden können. Um die Accounts samt der zugeordneten /home-Verzeichnisse dann wieder zu löschen, gibt es ein weiteres Script:

#!/bin/bash while read s; do userdel -r $s done < studenten.txt

Beispiel 7: Mehrere MySQL/MariaDB-Datenbanken ändern

In meiner Funktion als Datenbankadministrator muss ich gelegentlich an einer Menge gleichartiger Datenbanken Änderungen durchführen – z.B. bei 50 in ihrer Struktur gleichartigen Kundendatenbanken eine Spalte zu einer Tabelle hinzufügen. Manuell müsste ich dazu mit mysql dbname eine Verbindung zu jeder Datenbank herstellen und dann ALTER TABLE tblname ADD ... ausführen. Drei Zeilen bash-Code sorgen für mehr Effizienz:

#!/bin/bash for db in $(cat /etc/mydbs.txt); do mysql $db < updates.sql done

Das Script durchläuft alle in der Datei /etc/mydbs.txt zeilenweise aufgelisteten Datenbanken. Für jede Datenbank führt mysql nun die in der Datei updates.sql gespeicherten SQL-Kommandos aus. Die einzige Voraussetzung besteht darin, dass der Benutzer, der das Script ausführt, ohne explizite Passwortangabe eine Verbindung zu allen Datenbanken herstellen kann. Damit das funktioniert, müssen sich in der MySQL-spezifischen Datei .my.cnf (oder ab MySQL 5.6 in .mylogin.cnf) die erforderlichen Authentifizierungsdaten befinden.