Dieser Artikel ist größtenteils für alle Ubuntu-Versionen gültig.
Dieser Artikel soll Interessierten die grundlegenden Möglichkeiten des Bash-Skriptings nahebringen. So werden hier kurz die wichtigsten Konstrukte angesprochen und diese mit einigen praktischen Beispielen etwas vertieft, so dass ein besseres Verständnis ermöglicht wird.
Bash-Skripte sind sehr flexibel und lassen sich daher für die verschiedensten Aufgaben gut nutzen, zudem erleichtern sie die Arbeit, indem sie es uns erlauben, ständig wiederkehrende Prozesse zu automatisieren. Aufgrund dieser Eigenschaften nehmen sie auch eine zentrale Rolle im Alltag der Systemadministration ein. Hier im Wiki findet man bereits einige Beispiele, welche die breite Anwendbarkeit von Bash-Skripten ein wenig illustrieren.
Zuerst sollte man einen passenden Editor suchen, mit welchem man arbeiten möchte. Standardmäßig sind unter Ubuntu folgende installiert:
Wer mehr auf Konsolenfeeling steht, kann sich mal diese Editoren ansehen:
Eine Übersicht weiterer Editoren findet sich im Artikel Editoren.
Generell kann es nicht schaden, dass der Editor Syntax-Hervorhebung unterstützt, da dadurch die Übersichtlichkeit stark erhöht wird. Gerade für Anfänger ist das eine große Erleichterung.
Es ist sinnvoll, dass der Name des Skriptes dessen Funktion andeutet. Dabei wäre zu beachten, dass man keine Sonderzeichen verwendet und es sollte nicht schon einen gängigen Systembefehl mit diesem Namen geben. Man sollte sein Skript z.B. nicht cp nennen, da es diesen Befehl schon gibt. Das Ganze lässt sich vermeiden, indem man einfach testet, ob es schon ein Programm mit dem gewünschten Skriptnamen gibt, und das geht folgendermaßen:
which Skriptname
Erhält man hier eine Pfadangabe zurück, ist der Befehl bereits vorhanden und man sollte sich einen anderen Namen ausdenken. Ist dies nicht der Fall, kann man eine leere Datei mit dem Wunschnamen anlegen.
Damit ein Skript von einer Shell ausgeführt werden kann, muss es zuerst ausführbar gemacht werden. Dies geschieht z.B. in der Konsole durch folgenden Befehl.
chmod +x /pfad/zu/Mein_Skript
Mehr Informationen dazu findet man auch in Rechte.
Ist das Skript nun ausführbar gemacht, lässt es sich durch den Befehl.:
./Skriptname
aus dem selben Verzeichnis heraus ausführen [3].
Man kann das Skript, nachdem es ausführbar gemacht wurde, in einen Ordner (z.B. ~/bin) verlinken oder kopieren, welchen man der Umgebungsvariablen $PATH bekannt macht. Dies ist vor allem dann sinnvoll, wenn ein fertiges Skript von überall her einfach durch die Eingabe des Skriptnamens aufrufbar sein soll.
Es sollte aber (insbesondere bei von mehreren Personen benutzten Rechnern und Servern) darauf geachtet werden, dass Skripte, die in globalen Verzeichnissen (wie /usr/bin/) abgelegt werden, dem Benutzer root gehören und nur dieser in die Datei schreiben darf, ansonsten öffnet man eine Sicherheitslücke.
Es gibt zwei verschiedene Typen von Beispielen in diesem Artikel. Zum einen kann man interaktive Beispiele finden, welche dann folgendermaßen aussehen.
Befehl...
Ausgabe...
Dies bedeutet nichts anderes, als dass man dieses Beispiel einfach durch Eintippen in eine Konsole, Zeile für Zeile, nachvollziehen kann. Der zweite Typ ist der eines Skriptes. Diese beginnen immer mit einer Shebang, welche gleich noch erklärt wird, und sind zudem in einer Box mit Syntax-Highlighting gesetzt, damit man die Konstrukte besser erkennen kann. Diese sollten dann in eine Skriptdatei kopiert werden um sie auszuprobieren.
Natürlich ist es möglich, bei jedem Aufruf seines Skriptes die ausführende Shell von Hand festzulegen, doch diesen Aufwand möchte man nicht wirklich treiben. Die übliche Vorgehensweise ist, dies direkt im Skript anzugeben. Dies ermöglicht die Shebang, welche die erste Zeile eines Skriptes darstellt. Hier gibt man nun also an, welche Shell das Skript bearbeiten soll. Dies ist wichtig, da das Skript in der Syntax einer bestimmten Shell verfasst wird, von welcher es dann auch ausgeführt werden sollte.
Die Struktur ist folgende:
1 | #!/bin/bash
|
In diesem Fall legt man also fest, dass für die Ausführung des Skriptes die Bash verwendet werden soll.
Die Angabe kann hier nach Wunsch variieren und jede andere Shell beinhalten. Hierbei sollte man aber bedenken, dass man sich dann genauer mit der Syntax dieser auseinandersetzen sollte. Dieser Artikel hingegen basiert auf der Syntax der Bash. Es empfiehlt sich jedoch, wo immer möglich /bin/sh zu verwenden – damit wird das Skript (zumindest unter Ubuntu) in der Dash ausgeführt, eine die wesentlich schneller lädt als die Bash, dafür aber auch nicht alle Funktionen der Bash unterstützt.
Kommentare sind wichtig und man sollte regen Gebrauch davon machen. An schwierigen Passagen sollte man kleine Notizen hinterlegen um festzuhalten, was man sich dabei gedacht hat und wie das Konstrukt funktioniert. So erleichtert man sich selbst, z.B. nach längeren Pausen, oder aber auch Anderen, welche das Skript bearbeiten möchten, das Verständnis. Zudem lassen sich Kommentare auch gut als strukturgebendes Element einsetzen, was eine höhere Übersichtlichkeit ermöglicht.
Einfügen lassen sich Kommentare durch das Hash-Zeichen (#). Dies kann überall in einer Zeile stehen; die Bash ignoriert alles, was dahinter steht. Im Allgemeinen sieht das dann so aus.
# Dies ist ein Kommentar kein Kommentar # Dies ist ein Kommentar. ### Auf diese Weise kann man beispielsweise Abschnitte trennen ### ...
Variablen sind wichtig im Alltag des Bash-Skriptings. Sie verleihen einem Skript große Flexibilität. In ihnen werden z. B. Zeichenketten oder Zahlen abgelegt, welche noch weiterverarbeitet werden sollen.
Variablen werden folgendermaßen belegt:
var='hallo'
In diesem Beispiel wird der Variablen mit dem Namen var der Wert hallo zugewiesen. Hierbei sollte man darauf achten, die Zuweisung in Anführungszeichen zu setzen. Dies ist genau genommen jedoch nur notwendig, wenn die Zuweisung Leerzeichen oder andere Sonderzeichen enthält.
Variablen werden mit echo ausgegeben. Dabei muss ein Dollarzeichen $ vor dem Namen der Variablen eingefügt werden. Für die oben mit der Zuweisung hallo versehene Variable var würde sich also Folgendes ergeben:
echo "$var"
hallo
Man kann Variablen, die nicht mehr gebraucht werden, einfach mit dem Befehl unset wieder löschen. Dies wird am vorherigen Beispiel mit der Variable var weiterverfolgt.
echo "$var"
hallo
unset var echo "$var"
Gelegentlich kann es der Fall sein, dass man Variablen innerhalb eines Strings verwenden möchte. Dabei kommt es zu Schwierigkeiten, da die Konsole dann die Variable nicht mehr von dem sie umgebenden String unterscheiden kann. Um dieses Problem zu umgehen, verwendet man eine Schreibweise mit geschweiften Klammern. Ein Beispiel zeigt das Problem.
echo "$var"
hallo
echo "$varlolo"
Hier wird nach dem Anhängen eines beliebigen Strings an die Variable $var eine leere Variable ausgegeben. Dies liegt daran, dass die Bash versucht, die Variable mit dem Namen $varlolo auszugeben, welche nicht belegt und daher eben leer ist. Grenzt man die Variable jedoch durch die Schreibweise mit geschweiften Klammern ab, bekommt man folgendes Ergebnis.
echo "${var}lolo"
hallololo
Wie man sehen kann, wird hier der angehängte String "lolo" an die Variable angehängt, ohne sich mit dem String der Variablen zu vermischen und man erhält das gewünschte Ergebnis.
Oft wird diese Schreibweise gegenüber der ohne Klammern bevorzugt. Ich werde dies in diesem Artikel ab hier auch so handhaben.
Befehlssubstitution heißt hier das Zauberwort. Damit ist es möglich, die Ausgaben eines beliebigen Befehls in eine Variable zu schreiben. Ein Beispiel soll hier zeigen, wie man z.B. die Namen der Dateien im Homeverzeichnis in eine Variable einliest.
1 2 3 4 5 6 7 8 9 | #!/bin/bash #Ausgaben in Variable schreiben #Variablendefinition VERZEICHNIS="/home/foo/" NAMEN="$(ls -A ${VERZEICHNIS})" echo ${NAMEN} |
Wie man sieht, wird alles zwischen $( und ) stehende als Befehl ausgeführt und das Ergebnis zurückgeliefert. Somit gelangen die erhaltenen Daten dann durch die Zuweisung in die Variable $NAMEN. Danach werden die so gewonnenen Daten im Falle dieses Beispiels dann einfach allesamt auf einmal ausgegeben durch echo. Sie lassen sich so aber auch sehr gut über eine Schleife abarbeiten, dazu aber später mehr.
Es gibt eine alternative Schreibweise zu diesem Operator, die so genannten Backticks `: NAME="whoami". Der Einsatz empfiehlt sich aber nur bei kurzen Befehlen, da sonst der Quellcode leicht unleserlich wird.
Eine gewöhnungsbedürftige, aber doch sehr nette Funktion ist das Herausschneiden bestimmter Muster aus dem String einer Variablen.
| Schnitt-Funktionen | |||
| Funktion | Erklärung | ||
| ${variable%muster} | Entfernt rechts das kleinste passende Stück. | ||
| ${variable%%muster} | Entfernt rechts das größte passende Stück. | ||
| ${variable#muster} | Entfernt links das kleinste passende Stück. | ||
| ${variable##muster} | Entfernt links das größte passende Stück | ||
An einem kleinen Beispiel möchte ich hier kurz eine Anwendungsmöglichkeit erläutern.
pfadname="/var/www/index.html"
echo ${pfadname} /var/www/index.html
echo "Pfad: ${pfadname%/*}" Pfad: /var/www
echo "Dateiname: ${pfadname##*/}" Dateiname: index.html
Hier sieht man, wie leicht man an Teile einer Variablen herankommt, ohne die ursprüngliche Variable zu verändern. Ergänzend sei hier noch gesagt, dass ein Metazeichen (*) zum Einsatz gekommen ist, welches für eine beliebige Zeichenkette an dieser Stelle steht. Dies ist natürlich nicht die einzige Möglichkeit. Hier kann jedes Zeichen oder beliebige Folge von Metazeichen zum Einsatz kommen.
Es gibt diverse von der Shell selbst verwaltete Variablen. Diese Umgebungsvariablen lassen sich gut für verschiedene Aufgaben in unseren Skripten verwenden, wie z.B. einer Fehlerkontrolle von Programmen, indem man den Exitstatus prüft. In der folgenden Liste werden nur ein paar dieser Variablen aufgeführt, für weitere Informationen ist bitte der Artikel Umgebungsvariablen zu bemühen.
| Umleitungen | |||
| Variable | Erklärung | ||
| HOME | Enthält das Homverzeichnis des Benutzers. | ||
| PATH | Suchpfad für Kommandos. | ||
| PWD | Enthält das Aktuelle Verzeichnis. | ||
| ? | Enthält den Exitstatus des letzten Kommandos | ||
Arrays werden auch als Feldvariablen bezeichnet. Sie ermöglichen es, mehrere Werte gleicher Art innerhalb einer Variable abzuspeichern. Die einzelnen Werte stehen dabei in sogenannten Feldern des Arrays und können über den Index des Feldes einzeln angesprochen werden. Arrays lassen sich hervorragend über Schleifen verarbeiten, was etwas weiter unten noch beschrieben wird.
Um einen Wert in ein Array zu schreiben, ist es nötig den Index mit anzugeben. Allgemein sieht das folgendermaßen aus.
Arrayname[n]="Wert"
Ganz vorne steht der Name des Arrays, gefolgt vom Index, welcher durch eine Zahl in eckigen Klammern angegeben wird. Dann folgt wie auch schon bei den Variablen eine Zuweisung. In diesem Beispiel wird also der zugewiesene Wert "Wert" in das Feld "n" des Arrays "Arrayname" geschrieben.
Arrays lassen sich genau wie die Variablen mit Hilfe des Befehls echo auslesen. Hierbei sind jedoch verschiedene Möglichkeiten gegeben. Dieser Befehl ermöglicht den Zugriff auf das komplette Array.
echo ${array[*]} Durch den Stern in den Klammern für den Index erreicht man die Ausgabe des ganzen Arrays. Zudem gibt es auch die Möglichkeit auf einzelne Elemente zuzugreifen.
echo ${array[2]} Dabei wird einfach über die Angabe des Index das entsprechende Element ausgegeben.
Das folgende Beispiel zeigt etwas ausführlicher den Umgang mit einem Array.
array[1]="Hallo"
echo ${array[*]} Hallo
array[2]="Pingu"
echo ${array[*]} Hallo Pingu
array[1]="Bye"
echo ${array[*]} Bye Pingu
unset array
echo ${array[*]} An diesem Beispiel kann man ganz gut verschiedene Aspekte des Umgangs mit einem Array erkennen. Es zeigt wie ein Array angelegt wird, wie neue Felder belegt oder diese einfach verändert werden können. Schlussendlich wird das Array dann mit unset gelöscht.
Oft ist es notwendig dem Benutzer des Skriptes während dem Durchlauf mitzuteilen was gerade passiert oder ihn aufzufordern gewisse Angaben zu machen die für die weitere Arbeit des Skriptes notwendig sind. Den Möglichkeiten dies umzusetzen wird sich der folgende Abschnitt widmen.
Echo kann man verwenden um den Benutzer des Skriptes wissen zu lassen was gerade passiert, oder um ihn aufzufordern eine Eingabe zu machen. Hierbei sollte man darauf achten, den auszugebenden Text in Anführungszeichen zu setzen.
1 2 3 4 5 6 | #!/bin/bash #Hallo Welt Skript echo "Hallo Welt!" exit 0 |
Die Shebang wurde bereits beschrieben, ebenso Kommentare. Mit "echo" gibt man Nachrichten auf der Standardausgabe aus, welche somit also am Bildschirm sichtbar werden. Der exit Status gibt an, dass das Skript einwandfrei beendet wurde (also mit 0), aber exit 0 am Scriptende kann weggelassen werden. Mitten im Script kann man es mit exit vorzeitig beenden. Dieses Beispiel würde folgenden Output generieren.
Hallo Welt!
Mit Read ist man in der Lage Eingaben von der Standardeingabe einzulesen, und somit kann man also Benutzereingaben verarbeiten.
1 2 3 4 5 6 7 | #!/bin/bash #Begrüßung echo -n "Geben sie ihren Namen ein: " read NAME echo "Hallo: ${NAME}" |
In diesem Beispiel fordert man den Benutzer des Skriptes dazu auf eine Eingabe zu machen. Dabei dient der Schalter "-n" hinter echo dazu, die Ausgabe einer neuen Zeile zu verhindern. Dies bedeutet im Klartext, man schreibt dann den Namen direkt hinter den Doppelpunkt der ausgegebenen Aufforderung. Mit read liest man die Eingabe in die hinter read stehende Variable "NAME" ein. Die Ausgabe des Skriptes sieht dann wie folgt aus:
./begruessung.sh
Geben sie ihren Namen ein: Sab Hallo: Sab.
Umleitungen werden zu verschiedenen Zwecken verwendet. Hier werden nun ein Paar dieser Möglichkeiten aufgelistet. Mehr zum Thema Umleitungen findet sich im Wiki unter dem Artikel Umleitungen.
| Umleitungen | |||
| Operator | Erklärung | ||
| > | Dient der Umleitung der Standardausgabe in eine Datei. | ||
| >> | Dient ebenfalls der Umleitung in eine Datei, hängt jedoch die Ausgabe an eine bereits bestehende Datei an. | ||
| | | Mit dem Operator für eine Pipe lässt sich die Ausgabe eines Tools als Eingabe für ein anderes Tool verwenden. | ||
Das Test-Kommando wird dazu benötigt auf diverse Bedingungen hin zu testen. Es wird oft in Zusammenhang mit Programmführenden Konstrukten verwendet. Test kennt verschiedene Operatoren welche die Bedingungen des Testes beeinflussen. In der Folgenden Tabelle sind die Möglichkeiten von Test gelistet.
| Die Optionen des Test-Kommandos | |||
| Operator | Erklärung | ||
-d Verzeichnis | Ist wahr wenn "Verzeichnis" existiert. | ||
-f Datei | Ist wahr wenn Datei existiert. | ||
-w Datei | Ist wahr wenn die Datei existiert und Schreibzugriff erlaubt ist. | ||
-x Datei | Ist wahr wenn Datei existiert und Ausführbar ist. | ||
-n String | Ist wahr wenn String nicht leer ist. (Bsp. eine Variable) | ||
String1 == String2 | Ist wahr wenn String1 gleich String2 ist. | ||
Zahl1 -eq Zahl2 | Ist wahr wenn Zahl1 gleich Zahl2 ist. (-eq = equal) | ||
Zahl1 -lt Zahl2 | Ist wahr wenn Zahl1 kleiner Zahl2 ist. (-lt = less than) | ||
Zahl1 -gt Zahl2 | Ist wahr wenn Zahl1 größer Zahl2 ist. (-gt = greater than) | ||
Zahl1 -le Zahl2 | Ist wahr wenn Zahl1 kleiner oder gleich Zahl2 ist. (-le = less or equal) | ||
Zahl1 -ge Zahl2 | Ist wahr wenn Zahl1 größer oder gleich Zahl2 ist. (-ge = greater or equal) | ||
Zahl1 -ne Zahl2 | Ist wahr wenn Zahl1 nicht gleich Zahl2 ist. (-ne = not equal) | ||
! String | Ist wahr wenn String falsch ist, also eine Negation. | ||
Wie man das Testkommando nun richtig anwendet, wird gleich im Abschnitt Programmführung noch gezeigt.
Wie die Überschrift schon vermuten lässt geht es hier darum die Programmführung zu organisieren. Dabei nimmt man mit verschiedenen Konstrukten Einfluss auf den Ablauf des Skriptes.
Diese Anweisung macht es möglich nach einem Test auf eine Bedingung hin, eine entsprechende Maßnahme zu ergreifen. Das Konstrukt ist folgendermaßen aufgebaut.
1 2 3 4 | if [ Test-Bedingung ] then Befehl... fi |
In den Eckigen Klammern kann man jetzt jede der zuvor kennengelernten Option des Test-Befehls benutzen. Der Anweisungsblock wird mit fi geschlossen. Man kann dieses Konstrukt auch noch etwas erweitern, um eine alternative Reaktion auf einen Test hinzuzufügen.
1 2 3 4 5 6 | if [ Test-Bedingung ] then echo "Ok, alles in Ordnung." else echo "Ahrg...Fehler." fi |
Dies wird durch den Zusatz "else" ermöglicht. Zudem lässt sich dieses Konstrukt noch beliebig durch weitere Test-Bedingungen mit dem Zusatz "elif" erweitern. Dies sieht im Allgemeinen folgendermaßen aus.
1 2 3 4 5 6 7 8 9 | if [ Test-Bedingung ] then Befehl... elif [ Test-Bedingung ] then Befehl... else Befehl... fi |
Ein Beispiel zeigt wie man auf die Existenz einer Datei hin prüft und entsprechend des Ergebnisses eine Meldung dazu ausgibt.
1 2 3 4 5 6 7 8 9 | #!/bin/bash #Dateitest if [ -f /home/Pingu/test.txt ] then echo "Die Datei test.txt in Pingus Home ist da." else echo "Die Datei test.txt in Pingus Home fehlt." fi |
Oder das ganze mit einer Elif Konstruktion erweitert.
1 2 3 4 5 6 7 8 9 10 11 12 | #!/bin/bash #Dateitest if [ -f /home/Pingu/test.txt ] then echo "Die Datei test.txt in Pingus Home ist da." elif [ -d /home/Pingu/test.txt ] then echo "test.txt ist ein Verzeichnis" else echo "Die Datei test.txt in Pingus Home fehlt" fi |
Die Case Anweisung ist ein wichtiges Konstrukt um nach dem Test einer Variable entsprechend auf deren Inhalt zu reagieren. Sie ist übrigens auch eine elegante Alternative zu verschachtelten If-Else-Konstrukten, mit welchen man im Prinzip das gleiche Ergebnis erzielen könnte. Im Allgemeinen sieht dies folgendermaßen aus.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | case "${VARIABLE}" in wert1) Befehl... ;; wert2) Befehl... Befehl... Befehl... ;; wert3) Befehl... Befehl... ;; wert4) Befehl... ;; *) Befehl... Befehl... ;; esac |
Hier wird die Variable "VARIABLE" auf verschiedene Möglichkeiten hin überprüft welche in "wert1" bis "wert4" definiert werden. Zudem besteht die Möglichkeit, sollte keine der gegebenen Muster auf die Variable zutreffen eine Alternative Behandlung auszuführen. Dies ist durch den Abschnitt "*)" gegeben. Jeder Abschitt wird durch ein zweimaliges Semikolon ";" beendet und darf nicht vergessen werden.
Ein Beispiel zeigt dies nochmal etwas deutlicher.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | #!/bin/bash #namenstest echo -n "Bitte geben sie ihren Namen ein: " read NAME case "${NAME}" in Frank) echo "Hallo Frank" ;; Werner) echo "Hallo Werner" ;; Fritz) echo "Hallo Fritz" ;; *) echo "Hallo Fremder" ;; esac |
Es lassen sich auch Alternativen für eine Testbedingung angeben. Dazu wird der Pipe-Operator "|" verwendet. Dies könnte dann ungefähr folgendermaßen aussehen:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | #!/bin/bash #installer echo -n "Installer: Adminrechte werden benötigt. Wollen sie fortfahren?: " read ANSWER case "${ANSWER}" in Yes|yes|Y|y|"") echo "Ok los geht's" ;; No|no|N|n) echo "Abbruch." exit 1 ;; *) echo "Unbekannter Parameter" ;; esac |
So wird jetzt der erste Eintrag ausgeführt für Yes,yes,Y,y und zudem ist noch ein Leerzeichen angegeben welches durch die Anführungsstriche symbolisiert wird. Dies dient dazu, diesen Eintrag im Prinzip als Standard zu definieren, so wird dieser auch schon ausgeführt wenn man nur die Eingabetaste betätigt.
Es gibt in der Bash drei verschiedene Arten von Schleifen welche unterschiedlich gut für einen jeweiligen Typ von Aufgabe verwendet werden können. Grundsätzlich wird es durch sie möglich einen Befehl mehrmals auszuführen bis eine Bedingung eintritt oder solange eine Bedingung erfüllt ist. Diese drei Typen sind unten aufgezeigt, jedoch möchte ich mich auf die For- und While-Schleife beschränken.
For-Schleife
While-Schleife
Until-Schleife
Die wohl am häufigsten verwendete Schleife ist die For-Schleife. Die Syntax dafür sieht folgendermaßen aus.
1 2 3 4 5 6 | for variable in "Parameterliste" do Befehl1 Befehl2 usw... done |
Diese Schleife beendet sich, sobald das letzte Element der Übergebenen Parameterliste abgearbeitet ist. Ein Beispiel soll ihre Arbeitsweise verdeutlichen.
1 2 3 4 5 6 | #!/bin/bash # for-schleife for wort in "Hallo" "du" "Welt" "da" "draussen" do echo ${wort} done |
Dieses Beispiel erzeugt den folgenden Output.
./forschleife
Hallo du Welt da draussen
Jetzt möchte ich noch einmal auf das Beispiel von vorher zurück kommen bei dem man eine Variable mit Hilfe der Ausgabe eines Befehles gefüllt hat. Dies kann man sehr gut mit einer For-Schleife verbinden und so z.B. folgendes realisieren.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | #!/bin/bash #Variablendefinition INPUTDIR="/home/foo/Bilder/ZuBearbeiten/" OUTPUTDIR="/home/foo/Bilder/Bearbeitet/" PICS="`ls -A "${INPUTDIR}" | grep -i jpg`" #Abarbeiten der eingelesenen Bilder mit Hilfe einer For-Schleife #und dem Tool Imagemagick. for PIC in ${PICS} do echo "Bearbeite Bild: ${PIC}" echo "-------------------------------------------0" convert ${INPUTDIR}${PIC} -colorspace Gray ${OUTPUTDIR}${PIC} done |
Dieses Skript schreibt zuerst einmal alle Namen unserer Bilddateien im Ordner "INPUTDIR" in die Variable "PICS". Dazu wird hier die Ausgabe von ls durch eine Pipe (Dies ist eine Umleitung) an Grep weitergeleitet um die Bilddateien Herauszufischen. Dabei dient der Schalter "-i" bei "grep" dazu groß und Kleinschreibung zu ignorieren. Die Bilddateien werden dann über die For-Schleife abgearbeitet, wobei das Programm Imagemagick zum einsatz kommt. Hierbei werden alle Farbbilder in ihre Grauwerte umgerechnet und dann im Ordner "OUTPUTDIR" gespeichert. Man kann hieran ganz gut erkennen wie eine For-Schleife arbeitet. Jedes Element in der Liste wird einzeln herangezogen und abgearbeitet. Dazu bedient man sich einer weiteren Variable, welche diese jeweils aufnimmt (hier PIC). Die Schleife endet nach der Bearbeitung des letzten Listeneintrages.
Diese Methode lässt sich auch auf Arrays übertragen. Hier sieht jedoch das Konstrukt etwas anders aus. Hier einmal allgemein.
1 2 3 4 | for WERT in ${ARRAY[*]} do Befehl... done |
Die While-Schleife sieht im Allgemeinen folgendermaßen aus.
1 2 3 4 5 6 | while [ Test-Bedingung ] do Befehl1 Befehl2 usw... done |
Die While-Schleife wird solange ausgeführt wie eine Bedingung zutrifft oder eben nicht zutrifft. Ein Beispiel zeigt wie man mit einer While-Schleife wartet bis eine bestimmte Benutzereingabe stattgefunden hat. Hier kombiniert man einfach eine Case-Anweisung mit der While-Schleife.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | #!/bin/bash #Warten auf das richtige Ergebnis. echo "Frage: Was macht 2 + 2" echo "-------------------------o" echo "" while true do echo -n "Bitte ihre Eingabe:" read ANSWER case "${ANSWER}" in 4) echo "Richtig" break ;; *) echo "Falsch" ;; esac done |
Diese While-Schleife wurde mit "while true" initialisiert. Sie würde also im Prinzip nie enden. Einen Ausbruch hieraus ermöglicht uns der Befehl "break". Ist die Antwort richtig wird in diesem Fall also die Schleife beendet, andernfalls läuft sie einfach immer weiter. Es wäre noch zu erwähnen, dass diese Schleife natürlich auch mit jeder anderen Test-Bedingung initialisiert werden kann.
Man kann diverse Eingaben beim Start eines Skriptes mit an das Skript übergeben. Diese befinden sich dann in den Variablen $1 bis $9. So kann man dann also leicht innerhalb des Skriptes auf die übergebenen Parameter zugreifen. Dies kann auch geschickt dazu genutzt werden, das Skript in seiner Arbeit zu beeinflussen. Ein Beispiel soll hier zeigen wie dies generell funktioniert.
1 2 3 4 5 6 7 8 | #!/bin/bash # willkommen if [ ${1} == "Pingu" ] then echo "Hallo kleiner Pingu." else echo "Hallo ${1}" fi |
Und jetzt der Aufruf des Skriptes.
./willkommen.sh Pingu
Hallo kleiner Pingu.
./willkommen.sh Peter
Hallo Peter
Das Ganze lässt sich durch den Befehl shift noch weiter Ausbauen und man kann somit auch mehr als nur 9 Parameter übergeben. Mit Shift verschiebt man dabei die Angaben welche auf der Kommandozeile übergeben werden nach Abarbeitung immer eins weiter nach links. Hier bedient mach sich sogenannter short-circuit-Tests, die im Prinzip wie If arbeiten. Wie das funktioniert, soll hier nur ganz kurz gezeigt werden.
1 2 3 4 5 6 7 8 | #!/bin/bash while [ ${1} != '' ] do [ ${1} == "-b" ] && BACKUP="yes" && echo "Ok. Mache ein Backup" && shift [ ${1} == "-r" ] && RESTORE="yes" && echo "Ok. Mache ein Restore" && shift [ ${1} == "-c" ] && CLEAN="yes" && echo "Ok. Räume auf" && shift done |
Die Bedingung, mit der die While-Schleife initiiert wird, bedeutet soviel wie: Mach weiter solange "$1" nicht leer ist. Gibt man nun mehrere Parameter an das Skript mit, so wird immer der in Position "$1" liegende bearbeitet. Ist dies geschehen, wird durch "shift" der Parameter, welcher sich an der Stelle "$2" befindet, in die 1. Position gerückt, und die Schleife beginnt von vorne. Zudem wäre noch zu sagen, dass durch die Und-Zeichen "&&" Befehle in Short-Circuit-Tests verknüpft werden.
Desweiteren wäre in diesem Zusammenhang noch die Option extglob interessant, mit der sich recht komplexe testmuster bei der Übergabe von Parametern realisieren lassen. Wer aber nicht viel herumbasteln will, was die Übergabe von Parametern angeht, kann auf den Befehl "getopts" zurückgreifen, welcher schnell und komfortabel die Abarbeitung der Parameter übernimmt. Ein Beispiel soll zeigen wie "getopts" arbeitet.
1 2 3 4 5 6 7 8 9 10 11 12 | #!/bin/bash # Getopttest while getopts ':brcp:' OPTION ; do case ${OPTION} in b) echo "Ok mache ein Backup";; p) echo "Backuppfad ist: $OPTARG";; r) echo "OK mache ein Restore";; c) echo "Ok mache sauber danach";; *) echo "Unbekannter Parameter" esac done |
Wie man sehen kann arbeitet "getopts" mit bereits vertrauten Konstrukten zusammen. Es kommen eine While-Schleife und eine Case Anweisung zum Einsatz. Zur Notation von "getopts" wären folgende Dinge zu sagen. Der erste Doppelpunkt in der Parameterliste schaltet die Anzeige von Fehlermeldungen ab. Dann folgen normale Parameter. Besitzt ein Parameter einen Doppelpunkt hinter sich, bedeutet dies, dass er ein Argument benötigt. Hier in diesem Beispiel ist das bei "p" der Fall. Die Option "p" nimmt als Argument einen Pfad auf. Dieser wird dann in der Variable "${OPTARG}" gepeichert worüber man dann auch Zugriff darauf hat.
Funktionen helfen die Übersichtlichkeit zu verbessern, sie bieten die Möglichkeit mehrere Befehle zu logischen Gruppen zusammenfassen. Wurden sie einmal definiert sind sie im Skript durch Ihren Namen abrufbar. Dabei ist zu beachten, dass die Definition vor dem Aufruf stattgefunden haben muss. Dies führt dazu, dass man erst die Funktionen definiert und diese dann in einem Hauptteil, unten im Skript aufruft. Durch diese Vorgehensweise bleibt auch der logische Fluss des Skriptes übersichtlicher erhalten. Im Allgemeinen sieht eine Funktion dann folgendermaßen aus.
1 2 3 | funktionsname(){ Befehle... } |
Ein Beispiel zeigt wie dies aussehen könnte.
1 2 3 4 5 6 7 8 9 10 11 12 | #!/bin/bash # Variablendefinition PFAD="/home/pingu/" # Funktionsdefinitionen wievieledateien(){ ANZAHL="`ls -lA ${PFAD} | wc -l`" echo "Es befinden sich ${ANZAHL} Dateien und Ordner in ${PFAD}" } # Hauptteil wievieledateien |
Starten man nun dieses Skript erhält man folgende Ausgabe.
./wievieledateien
Es befinden sich 90 Dateien und Ordner in /home/pingu/
Zudem ist es möglich Parameter an eine Funktion zu übergeben, was diese noch viel mächtiger macht. Dies funktioniert genau wie bei der Übergabe von Parametern an ein Skript, indem man beim Aufruf die Parameter an den Befehl anhängt. Das folgende Beispiel baut das vorherige dahingehend aus.
1 2 3 4 5 6 7 8 9 10 | #!/bin/bash # Funktionsdefinitionen wievieledateien(){ # Substitution der Parametervariablen ZAEHLPFAD="${1}" ANZAHL="`ls -lA ${ZAEHLPFAD} | wc -l`" echo "Es befinden sich ${ANZAHL} Dateien und Ordner in ${ZAEHLPFAD}" } # Hauptteil wievieledateien /home/pingu/ |
Es ist möglich ein Skript durch zusätzliche Programme grafisch aufzuwerten. Dafür gibt es verschiedene Möglichkeiten. Wie das ganze funktioniert soll der folgende Abschnitt klären. Hauptsächlich soll dabei auf das Programm "Dialog" eingegangen werden.
Dialog bietet eine, immer noch konsolenbasierte, Aufbereitung der darzustellenden Daten. Dabei reicht das Repertoire von einfachen Entscheidungsfenstern (Ja/Nein) über verschiedene Auswahlmenüs bis hin zur Möglichkeit der Darstellung einer Dateiauswahlliste. Da Dialog standardmäßig nicht installiert ist sollte man dies erst einmal tun. Der folgende Befehl installiert Dialog.
sudo apt-get install dialog
Ein einfaches Beispiel soll die Arbeit mit Dialog illustrieren.
1 2 3 4 | #!/bin/bash # Ja oder Nein dialog --yesno "Bist du ein Linuxfan?" 15 60 |
Dieses Beispiel erzeugt eine Box von 60 Zeichen Breite und 15 Zeilen Höhe mit dem in Anführungsstrichen stehenden Fragentext und zwei Buttons für "Yes" und "No". Dialog speichert den Rückgabewert für die getroffene Auswahl in der allgemeinen Rückgabevariable "$?". Im Fall dieser Yes/No-Box bedeuten die Rückabewerte 0 ja, 1 nein und 255 Abbruch ohne Auswahl. Jetzt möchte ich einen Schritt weiter gehen und dieses Beispiel ausbauen.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | #!/bin/bash # erweiterter Dialog dialog --backtitle Umfrage --title "Erste Frage" --yesno "Sind sie ein Linuxfan?" 15 60 ANTWORT=${?} if [ ${ANTWORT} -eq "255" ] then echo "Abgebrochen" exit 255 fi if [ ${ANTWORT} -eq "1" ] then dialog --backtitle Umfrage --title Kritikbox --msgbox "Na dann wird es aber allerhöchste Zeit!" 15 40 fi if [ ${ANTWORT} -eq "0" ] then dialog --backtitle Umfrage --title Schmeichelbox --msgbox "Hm. Das hab ich mir fast schon gedacht!" 15 40 fi |
Hier tauchen neue Optionen auf. Die Eine ist "--backtitle". Wie der Name schon sagt, ist diese dafür zuständig den Titel des Hintergrundes zu setzen. Die Andere ist "--title" welche für die Überschrift der eigentlichen Box zuständig ist. Zusätzlich zur "--yesno"-Box hat man es hier noch mit zwei Messageboxen (--msgbox) zu tun, welche nur eine Nachricht darstellen und dann über einen OK-Button geschlossen werden können. Diese Messageboxen tauchen je nach der im ersten Fenster gegebenen Antwort auf. Jetz noch etwas komplexer.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | #!/bin/bash # erweiterter Dialog dialog --backtitle Umfrage --title "Erste Frage" --yesno "Sind sie ein Linuxfan?" 15 60 ANTWORT=${?} if [ ${ANTWORT} -eq "255" ] then echo "Abgebrochen" exit 255 fi if [ ${ANTWORT} -eq "1" ] then dialog --backtitle Umfrage --title Kritikbox --msgbox "Na dann wird es aber allerhöchste Zeit!" 15 40 fi if [ ${ANTWORT} -eq "0" ] then dialog --backtitle Umfrage --title Lobbox --msgbox "Hm. Das hab ich mir fast schon gedacht!" 15 40 AUSWAHL=`dialog --stdout --backtitle Umfrage --title Details --radiolist "Welche Desktopumgebung bevorzugen Sie? Sie können nur eine wählen." 16 60 5 \ "Gnome" "Die Gnome Desktop Umgebung" off \ "KDE" "Die KDE Desktopumgebung" on \ "Sonstige" "Eine andere hier nicht gelistete" off` fi case ${AUSWAHL} in Gnome) dialog --backtitle Umfrage --title Reaktion --msgbox "Sehr schön Gnome!" 15 40 ;; KDE) dialog --backtitle Umfrage --title Reaktion --msgbox "Sehr schön KDE!" 15 40 ;; Sonstige) dialog --backtitle Umfrage --title Reaktion --msgbox "Hm Sonstige!" 15 40 ;; esac |
Dieses Beispiel geht nach einer positiv ausgefallenen Antwort noch etwas weiter. Mit Hilfe einer Radioliste "--radiolist" wird die bevorzugte Desktopumgebung erfragt. Eine Radioliste zeichnet sich dadurch aus, dass man nur einen der Einträge aktivieren kann. Möchte man mehrere Einträge aktivieren können sollte man statdessen "--checkbox" verwenden. Die Antwort aus unserer Radioliste wird mit hilfe von Backticks in eine Variable geschrieben um sie danach weiterverarbeiten zu können. Dies geschieht durch eine Case Anweisung. Was noch wichtig wäre zu sagen ist, dass die Option "--stdout" beim schreiben in eine Variable nicht vergessen werden sollte. Diese sorgt für eine Ausgabe auf dem Bildschirm. Wäre sie nicht gesetzt würde der Dialog nicht erscheinen.
Als weitere Möglichkeiten zur Erstellung grafischer Unterstützungen für Bash-Skritpe wären noch die Programme Zenity und KDialog zu nennen. Wie dieses Programme funktionieren wird genauer in deren jeweiligen Artikeln hier im Wiki beschrieben.
Diese Revision wurde am 3. März 2010 um 13:42 Uhr
von dAnjou erstellt.
Dieser Seite wurden folgende Begriffe zugeordnet:
Shell, Programmierung, Einsteiger
2004 – 2010 ubuntuusers.de • Einige Rechte vorbehalten