[[Vorlage(Getestet, general)]] {{{#!vorlage Wissen [:Terminal: Ein Terminal öffnen] [:Shell: Grundsätzlicher Umgang mit der Shell] [:Programme starten: Ein Programm starten] }}} [[Inhaltsverzeichnis(2)]] [[Bild(Wiki/Icons/terminal.png, 48, align=left)]] Dieser Artikel soll Interessierten die grundlegenden Möglichkeiten des Bash-Skriptens nahebringen. So werden hier kurz die wichtigsten Konstrukte angesprochen und diese mit einigen praktischen Beispielen etwas vertieft. = Einsatzgebiete von Bash-Skripten = Bash-Skripte sind geeignet um Programme, gemäß der eigenen Bedürfnisse, miteinander zu kombinieren und sie spielen eine zentrale Rolle im Alltag der Systemadministration. Hier im Wiki findet man bereits einige [:Skripte: Beispiele], welche die breite Anwendbarkeit von Bashskripten ein wenig illustrieren. = Vorbereitung = == Editor == Zuerst sollte man einen Editor suchen, mit welchem man arbeiten möchte. Standardmäßig sind unter Ubuntu folgende installiert: * In Ubuntu (GNOME) : [:gedit:] * In Kubuntu (KDE) : [:Kate:] * In Xubuntu (XFCE) : [:Mousepad:]^^(*)^^ * In Lubuntu (LXDE) : [:Leafpad:]^^(*)^^ Für die Konsole sind empfehlenswert: * [:Vim:] (Nicht leicht für Anfänger, aber trotzdem sehr empfehlenswert) * [:Nano:] Eine Übersicht weiterer Editoren findet sich im Artikel [:Editoren:]. {{{#!vorlage Hinweis ^^(*)^^Wenn der Editor Syntax-Hervorhebung beherrscht, erhöht sich die Übersichtlichkeit. Gerade für Anfänger ist das eine große Erleichterung. Mousepad und Leafpad bieten dies nicht. }}} == Name des Skriptes == 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: {{{#!vorlage Befehl type Skriptname }}} Erhält man hier ein Resultat, ist der Name bereits vorhanden, und man sollte sich einen anderen ausdenken. == Ausführbar machen und aufrufen == Damit ein Skript ohne Angabe des Interpreters ausgeführt werden kann, muss es zuerst ausführbar gemacht werden. Dies geschieht z.B. in der Konsole durch folgenden Befehl. {{{#!vorlage 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.: {{{#!vorlage Befehl ./skriptname }}} aus dem selben Verzeichnis heraus ausführen [3]. Dabei sollte nicht vergessen werden, dass sich Skripte auch ausführen lassen, wenn sie selbst nicht ausführbar gemacht sind. Ein Aufruf des Interpreters mit dem Skriptnamen als Argument führt den Skriptinhalt ebenfalls aus: {{{#!vorlage Befehl bash skriptname }}} {{{#!vorlage Hinweis Man kann das Skript, nachdem es ausführbar gemacht wurde, in einen Ordner (z.B. '''~/bin/''') verlinken oder verschieben, welchen man der Umgebungsvariablen [:Umgebungsvariable: $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. In Ubuntu sorgt ein Mechanismus in der Datei '''~/.profile''' dafür, dass '''~/bin/''' automatisch an den Anfang von [:Umgebungsvariable: $PATH] gesetzt wird, sobald dieses Verzeichnis existiert. Es sollte (insbesondere bei von mehreren Personen benutzten Rechnern und Servern) darauf geachtet werden, dass Skripte, die in globalen Verzeichnissen wie '''/usr/local/bin/''' abgelegt werden, dem Benutzer ''root'' gehören und nur dieser in die Datei schreiben darf, ansonsten öffnet man eine Sicherheitslücke. Auch Skripte in '''~/bin/''' können ein Sicherheitsproblem sein, wenn sie den gleichen Namen wie Systemprogramme haben. So könnte ein Angreifer mit einfachen Nutzerrechten beispielsweise ein Skript in diesen Ordner legen, das einfach '''sudo''' heißt und damit das Passwort des Nutzers abfangen. }}} == Zu den Beispielen == Es gibt zwei verschiedene Typen von Beispielen in diesem Artikel. Zum einen kann man interaktive Beispiele finden, welche dann folgendermaßen aussehen. {{{#!vorlage Befehl 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 [wikipedia:Shebang: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. = Das erste Skript = == Die Shebang == Prinzipiell wird ein Skript über seinen Interpreter gestartet. Das Skript selbst erzeugt keinen [wikipedia:Prozess_(Informatik):Prozess], sondern nur der ausführende Interpreter – zum Beispiel die Bash. Das heißt, zur Ausführung eines Skriptes muss der Interpreter gestartet und ihm mitgeteilt werden, welches Skript er abarbeiten soll. Ein solcher Aufruf hätte diese Form: {{{#!vorlage befehl bash mein_skript.sh }}} Natürlich ist es möglich, bei jedem Aufruf seines Skriptes die ausführende [:Shell:] erneut festzulegen, doch diesen Aufwand möchte man oft nicht treiben. Die übliche Vorgehensweise ist, die zu verwendende Shell direkt im Skript anzugeben. Dies wird durch den Shebang ermöglicht, welcher in die erste Zeile eines Skriptes gehört. Hier gibt man 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: {{{#!code bash #!/bin/bash }}} In diesem Fall legt man also fest, dass für die Ausführung des Skriptes die Bash verwendet werden soll. Die Raute zu Beginn der Zeile legt lediglich fest, dass der Rest der Zeile ein [#Kommentare-einfuegen Kommentar] ist. Die ausführende Shell beachtet den Shebang also gar nicht weiter. Der [:Kernel:] hingegen erkennt beim Start der Datei durch diese Raute gefolgt vom Ausrufezeichen, dass dieses Skript von einem bestimmten Interpreter ausgeführt werden soll. Der Pfad dieses Interpreters folgt nach `#!`, hier also '''/bin/bash'''. Hat das derart vorbereitete Skript Ausführungsrechte und wird es dann über {{{#!vorlage befehl ./mein_skript.sh }}} aufgerufen, dann ist dieser Aufruf äquivalent hierzu: {{{#!vorlage befehl /bin/bash ./mein_skript.sh }}} Im Shebang muss ein absoluter oder relativer Pfad angegeben werden, da die `$PATH`-Umgebungsvariable nicht beachtet wird. Es genügt also nicht, hier nur `#!bash` zu schreiben. Benutzt man eine andere Shell, so ändert man den Shebang entsprechend ab, etwa zu `#!/bin/zsh`. Dieser Artikel bezieht sich aber nur auf die Bash, weshalb '''/bin/bash''' verwendet wird. Damit wird deutlich die Aussage getroffen: „Dies ist ein Bash-Skript.“ In vielen anderen Anleitungen findet sich jedoch '''/bin/sh''' in der Shebang. Was hat es damit auf sich? Die Bash ist eine komplexere Shell, die nicht auf allen unixoiden Systemen zur Verfügung steht. '''/bin/sh''' hingegen ist laut [wikipedia:POSIX:POSIX-Standard] ein Muss. Hinsichtlich des Funktionsumfangs stimmen die von POSIX geforderte sh und die Bash aber nicht überein, denn die Bash kann wesentlich mehr als sh. Verwendet man '''/bin/sh''' in der Shebang, wird also eine andere Aussage gemacht: „Dieses Skript entspricht dem POSIX-Standard.“ Grundsätzlich gilt: Der Shebang muss einen Interpreter angeben, der alle im Skript benutzten Funktionen beherrscht. Man kann nicht allgemein sagen, „Shellskripte können '''/bin/sh''' verwenden“. Man muss sich stattdessen darüber im Klaren sein, dass man einen zur gewählten Syntax passenden Interpreter wählen muss. Ein falsch gewählter Shebang ist ein häufiger Fehler. Ein Bash-Skript, das '''/bin/sh''' als Shebang verwendet, funktioniert lediglich in den Ausnahmefällen, in denen nur Kommandos der Schnittmenge der beiden Shells verwendet werden. {{{#!vorlage Hinweis Unter Ubuntu ist '''/bin/sh''' ein [:ln#Symbolische-Verknuepfungen:symbolischer Link] auf die [:Dash:]. Welche Konsequenzen das hat, wird unter anderem im [http://wiki.ubuntu.com/DashAsBinSh englischen Ubuntu-Wiki Eintrag zur DASH] {en} erklärt. }}} == Kommentare einfügen == 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. Kommentare beginnen mit dem Hash-Zeichen (#). Dies kann überall in einer Zeile stehen; außer innerhalb von Quotes. Die Bash ignoriert alles, was dahinter steht. Allgemein sieht das dann so aus: {{{ # Dies ist ein Kommentar kein Kommentar # Dies ist ein Kommentar. echo "auch # kein Kommentar innerhalb" # , wohl aber außerhalb ... }}} == Variablen – Teil 1 == Variablen sind symbolische Namen für Werte und verleihen einem Skript große Flexibilität. Sie erlauben es, einen Wert an nur einer Stelle zu ändern und den Wert überhaupt zu ändern. === Variablen belegen === Variablen werden folgendermaßen belegt: {{{#!vorlage Befehl message=hallo }}} In diesem Beispiel wird der Variablen mit dem Namen `message` der Wert `hallo` zugewiesen. Wenn die Zuweisung Leerzeichen oder andere Sonderzeichen enthält, sind Anführungszeichen notwendig: {{{#!vorlage Befehl message="hallo Welt" }}} oder das maskieren mittels Backslash: {{{#!vorlage Befehl message=hello\ world }}} === Variablen lesen === Variablen werden mit echo ausgegeben. Dabei muss man ein Dollarzeichen `$` vor den Namen der Variablen setzen. Für die oben mit der Zuweisung `hallo` versehene Variable `message` würde sich also Folgendes ergeben: {{{#!vorlage Befehl echo "$message" }}} {{{ hallo Welt }}} === Variablen löschen === Man kann Variablen, die nicht mehr gebraucht werden, einfach mit dem Befehl `unset` wieder löschen. Dies wird am vorherigen Beispiel mit der Variable `message` weiterverfolgt. {{{#!vorlage Befehl echo "$message" }}} {{{ hallo Welt }}} {{{#!vorlage Befehl unset message echo "$message" }}} {{{ }}} === Variablen abgrenzen === Gelegentlich kann es der Fall sein, dass man Variablen innerhalb einer Zeichenkette verwenden möchte. Dabei kommt es zu Schwierigkeiten, da die Konsole dann die Variable nicht mehr von dem sie umgebenden Zeichenkette unterscheiden kann. Um dieses Problem zu umgehen, verwendet man eine Schreibweise mit geschweiften Klammern. Ein Beispiel zeigt das Problem. {{{#!vorlage Befehl echo "$message" }}} {{{ hallo }}} {{{#!vorlage Befehl echo "$messagelolo" }}} {{{ }}} Hier wird nach dem Anhängen einer beliebigen Zeichenkette an die Variable `$message` eine leere Variable ausgegeben. Dies liegt daran, dass die Bash versucht, die Variable mit dem Namen `$messagelolo` 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. {{{#!vorlage Befehl echo "${message}lolo" }}} {{{ hallololo }}} Wie man sehen kann, wird hier die angehängte Zeichenkette `lolo` an die Variable angehängt, ohne sich mit der Zeichenkette der Variablen zu vermischen, und man erhält das gewünschte Ergebnis. == Quoting == Erklärung des Quotings im Video [youtube:6kVEIiPaNR4?t=11m5s:Axel Beckert (Ubucon 2010)] {de} === Befehle === Einige Sonderzeichen, Zeilenumbrüche und Leerzeichen haben in Shellskripten eine besondere Bedeutung. Bei einem einfachen Beispiel wird dies vielleicht noch nicht deutlich: {{{#!vorlage befehl echo Hallo, Welt }}} {{{ Hallo, Welt }}} Doch spätestens, wenn dieser Befehl mit einigen Leerzeichen „geschmückt“ wird, zeigt sich sonderbares Verhalten: {{{#!vorlage befehl echo Hallo, Welt }}} {{{ Hallo, Welt }}} Die zusätzlichen Leerzeichen gehen in der Ausgabe also verloren. Möchte man sie erhalten, muss die auszugebende Zeichenkette in Anführungszeichen gesetzt werden: {{{#!vorlage befehl echo "Hallo, Welt" }}} {{{ Hallo, Welt }}} Die Anführungszeichen (engl. „Quotes“) haben hier folgende Bedeutung: Sie entbinden die Leerzeichen zwischen den beiden Worten von ihrer Funktion als Worttrenner. Dieser Vorgang, also das Entbinden eines bestimmten Zeichens von einer Sonderfunktion, wird auch Maskierung genannt. Ein oder mehrere aufeinander folgende Leerzeichen haben im Normalfall die Funktion, Argumente für Befehle voneinander abzugrenzen. In der Vorverarbeitungsphase des Befehls würden ohne Anführungszeichen dadurch die beiden Zeichenketten „Hallo,“ und „Welt“ zu zwei eigenständigen Argumenten für den `echo`-Befehl werden. Bei anderen Befehlen ist dies ein ganz natürliches und gewünschtes Verhalten: {{{#!vorlage befehl ls -al /usr /tmp }}} Hier sorgen die Leerzeichen dafür, dass `ls` die drei Argumente `-al`, `/usr` und `/tmp` erhält. Die Shell übernimmt also die Aufgabe, derlei Zeichenkettenverarbeitung an zentraler Stelle zu erledigen. Dadurch braucht `ls` dies nicht selbst zu tun – andere Programme wie `cp`, `mv` oder `rm` müssten es dann nämlich auch selbst vornehmen. Das heißt auch, dass Programme keine Dateinamensmuster zu sehen bekommen: {{{#!vorlage befehl ls -al *.ogg }}} Die ''ausführende Shell'' löst das Muster `*.ogg` auf und `ls` sieht nur noch die resultierende Liste an Dateinamen. Analog wird beispielsweise mit Variablennamen oder Umleitungen verfahren. Derartige Vorverarbeitungen von der Shell wie die Auflösung von Mustern oder die Auftrennung in einzelne Worte werden durch Anführungszeichen verhindert. Das heißt, man könnte bei „Hallo, Welt“ genauso gut Folgendes schreiben: {{{#!vorlage befehl echo Hallo," "Welt }}} Dieses Beispiel soll noch einmal verdeutlichen, dass Anführungszeichen ''nicht'' – wie in anderen Sprachen üblich – zur Kennzeichnung von Zeichenketten verwendet werden. Stattdessen heben sie die Sonderbedeutung von bestimmten Zeichen auf. Möchte man also zum Beispiel den Stern von seiner Bedeutung „passe auf alle Zeichenketten“ (was im folgenden Beispiel auf „alle nicht-versteckten Dateien im aktuellen Verzeichnis“ hinausläuft) entbinden, dann muss man Anführungszeichen um ihn herum setzen. Man vergleiche: {{{#!vorlage befehl echo * }}} {{{ bin include lib local man sbin share src var }}} Und: {{{#!vorlage befehl echo "*" }}} {{{ * }}} Auch Zeilenumbrüche können wörtlich übernommen werden, wenn sie innerhalb von Anführungszeichen stehen – sie verlieren also ihre Bedeutung als „Ende des Befehls“: {{{#!vorlage befehl echo "Dies ist eine mehrzeilige Ausgabe. Man hält es nicht für möglich." }}} {{{ Dies ist eine mehrzeilige Ausgabe. Man hält es nicht für möglich. }}} === Variablen === Bisher wurden lediglich Befehle betrachtet. Bei der Verwendung von Variablen findet jedoch ein vergleichbarer Prozess statt. Dies gilt einerseits für die Variablenzuweisung und andererseits auch für das Auslesen von Variablen: {{{#!code bash foo="Hallo, Welt" echo "$foo" }}} Ohne Anführungszeichen bei `echo` würden wieder nur die Leerzeichen verlorengehen: {{{#!vorlage befehl echo $foo }}} {{{ Hallo, Welt }}} Bei der Zuweisung jedoch handelt es sich um einen Sonderfall. Ohne Maskierung erhält man einen Fehler: {{{#!vorlage befehl foo=Hallo, Welt }}} {{{ -bash: Welt: command not found }}} Dies passiert deshalb, weil Variablenzuweisungen auch nur für einen einzelnen Befehl gelten können, nämlich sofern sie unmittelbar vor ihm aufgeschrieben werden. Genau das ist hier der Fall. Die Zuweisung ist `foo=Hallo,` und der Befehl wäre dann `Welt`. Da es ein solches Programm nicht gibt, erhält man einen Fehler. Der Vollständigkeit zuliebe sei hier ein „echtes“ Beispiel erwähnt, das korrekten Gebrauch von dieser Funktionalität macht. Mit `date` kann der aktuelle Wochentag ausgegeben werden: {{{#!vorlage befehl date +%A }}} {{{ Montag }}} Auf einem deutschen System ist diese Ausgabe wie erwartet auf Deutsch. Man kann nun die Variable `$LC_ALL` vorübergehend nur für den Aufruf von `date` umbelegen, sodass eine andere [:Spracheinstellungen:locale] (Lokalisation: Sprache, Datumsformat, ...) benutzt wird. Hier wird die besondere „C“-Locale verwendet, welche eine englische Ausgabe bewirkt: {{{#!vorlage befehl LC_ALL=C date +%A }}} {{{ Monday }}} Doppelte Anführungszeichen reichen jedoch nicht in allen Situationen aus, denn sie heben nicht die Bedeutung ''aller'' Sonderzeichen auf. Wie bereits am letzten Beispiel ersichtlich, behält das Dollarzeichen seine Sonderbedeutung. Was kann man nun tun, wenn man tatsächlich die Zeichenkette „$foo“ ausgeben möchte? Hierzu gibt es einfache Anführungszeichen. Sie entbinden alle Zeichen von ihren Sonderfunktionen, bis auf das einfache Anführungszeichen selbst. {{{#!vorlage befehl echo '$foo' }}} {{{ $foo }}} \\ {{{#!vorlage hinweis Variablen, die mit Dateinamen belegt sein können, sollten in doppelten Anführungszeichen stehen. In Dateinamen sind alle Zeichen erlaubt außer der binären Null (\0) und dem Pfadtrenner '/', insbesondere also auch solche, die von der Shell ausgewertet werden würden wie '*' und '?'. Dies kann ungewünschte Folgen haben. Insbesondere gilt dies natürlich für Dateinamen, die Leerzeichen enthalten, die leicht als Ende des Dateinamens missverstanden werden. }}} === Maskierung mit Backslashes === Eine Alternative zum Setzen von Anführungszeichen stellt der Rückwärtsstrich ("backslash") dar. Das ihm folgende Zeichen wird wörtlich übernommen: {{{#!vorlage befehl echo \$foo }}} {{{ $foo }}} Backslashes werden bei längeren Zeichenketten oder Befehlen jedoch schnell unübersichtlich. Deshalb sind in den meisten Fällen Anführungszeichen die bessere Wahl. Ein Backslash kann allerdings genutzt werden, um (doppelte) Anführungszeichen zu schachteln: {{{#!vorlage befehl echo "Er sprach: \"Hallo, Welt\"" }}} {{{ Er sprach: "Hallo, Welt" }}} Aber Vorsicht: Einfache Anführungszeichen lassen sich nicht schachteln. Dies liegt daran, dass sie, wie bereits erwähnt, alle Zeichen außer sich selbst wörtlich übernehmen. Folglich verliert auch der Backslash seine Funktion. Im folgenden Beispiel haben also die einfachen Anführungszeichen Vorrang gegenüber dem Backslash – sie heben seine Wirkung auf und nicht er ihre. {{{#!vorlage befehl echo 'Dies ist ein Backslash: \' }}} {{{ Dies ist ein Backslash: \ }}} Möchte man ein einfaches Anführungszeichen in einer solchen Situation ausgeben, so muss man zuerst den von ihnen umschlossenen Bereich beenden und danach einen neuen Bereich beginnen, der von ''doppelten'' Anführungszeichen umschlossen ist: {{{#!vorlage befehl echo 'So sprach'"'"'s und ging ohne einen Backslash (\) weiter.' }}} {{{ So sprach's und ging ohne einen Backslash (\) weiter. }}} Noch einmal im Detail: {{{ echo 'So sprach'"'"'s und ging ohne einen Backslash (\) weiter.' └────┬────┘└┬┘└─────────────────────┬─────────────────────┘ │ │ │ │ │ └ Dritter Bereich: Wieder von ' │ │ umschlossen, der Backlash │ │ verliert seine Sonderbedeutung. │ │ │ └ Zweiter Bereich: Von " umschlossen, enthält ein │ einzelnes '. │ └ Erster Bereich: Von ' umschlossen. }}} Einfacher ist natürlich der Verzicht auf die überflüssigen Anführungsstriche: {{{#!vorlage befehl echo So sprach"'"s und ging ohne einen Backslash '(\)' weiter. }}} Alternative Wege __mit__ Quoting: {{{#!vorlage Befehl echo 'So sprach'\''s und ging ohne einen Backslash (\) weiter.' echo "So sprach's und ging ohne einen Backslash (\) weiter." }}} == Variablen – Teil 2 == === Ausgaben in eine Variable schreiben === Durch eine Befehlssubstitution ist es möglich, die Textausgaben eines beliebigen Befehls in eine Variable zu schreiben. Ein Beispiel soll hier zeigen, wie man die Liste der Player im System anzeigt. {{{#!code bash #!/bin/bash #Ausgaben in Variable schreiben #Variablendefinition suchwort=player liste=$(apropos $suchwort) echo " Player-Liste:" echo "$liste" }}} Wie man sieht, wird alles zwischen `$(` und `)` stehende als Befehl ausgeführt und die Ausgabe in die Variable `$liste` gespeichert. Danach werden die so gewonnenen Daten im Falle dieses Beispiels auf einmal durch `echo` ausgegeben. Sie lassen sich so aber auch sehr gut über eine Schleife abarbeiten, dazu aber später mehr. {{{#!vorlage Hinweis Es gibt eine alternative Schreibweise zu diesem Operator, die Backticks '''\`''': `NAME=\`whoami\``. Der Einsatz empfiehlt sich aber nicht, da sie sich nicht ohne kompliziertes Maskieren schachteln lassen und, je nach Schriftart, schlecht vom Apostroph zu unterscheiden sind. }}} Einfache und doppelte Gänsefüßchen unterscheiden sich beim Einschließen von Variablen darin, dass bei `"..."` Variablen expandiert werden, bei `'...'` nicht. {{{#!vorlage Warnung Es ist verführerisch, die Ausgabe des Befehls `ls` im Skript zu verarbeiten. Das wäre aber gleich in mehrerer Hinsicht gefährlich: * Leerzeichen und Zeilenwechsel in Dateinamen verursachen oft Probleme, da sie als Ende des Dateinamens interpretiert werden oder weil man die Eingabe zeilenweise verarbeitet und so den Dateinamen durchschneidet. * Abhängig vom ''locale'' und einigen anderen Einstellungen ersetzt `ls` manche Zeichen durch ein oder mehrere "?". Dies ist aber bei ''bash'' der Platzhalter für "jedes Zeichen" und führt leicht zu Mehrdeutigkeiten, ebenso wie der '*', sofern er im Dateinamen vorkommt. }}} Dateien und Verzeichnisse lassen sich gefahrlos mit den ''bash''-eigenen Funktionen abfragen ([:basename:] und [:dirname:] sind eigenständige Befehle): {{{#!code bash #!/bin/bash for file in ./*/*.png ; do echo "Diese Datei: $file" fname=$(basename "$file") echo "hat den Namen: $fname" fdir=$(dirname "$file") echo "und steht im Verzeichnis: $fdir" done }}} === Abschneiden von Mustern === Eine gewöhnungsbedürftige, aber doch sehr nette Funktion ist das Herausschneiden bestimmter Muster aus der Zeichenkette einer Variablen. ||<-4 cellstyle="background-color: #E2C890;" :> '''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 soll nun eine Anwendungsmöglichkeit erläutert werden. {{{#!vorlage Befehl pfadname="/var/www/index.html" echo $pfadname }}} {{{ /var/www/index.html }}} {{{#!vorlage Befehl echo "Pfad: ${pfadname%/*}" }}} {{{ Pfad: /var/www }}} {{{#!vorlage Befehl 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. === Umgebungsvariablen === Es gibt diverse von der Shell selbst verwaltete Variablen. Diese Umgebungsvariablen lassen sich gut für verschiedene Aufgaben in Skripten verwenden, wie z.B. einer Fehlerkontrolle von Programmen, indem man den Rückgabewert ("exit status") 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. ||<-4 cellstyle="background-color: #E2C890;" :> '''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 || == Rechnen mit der Shell == Die Shell kann durch die interne Funktion '''$([color=black]([/color] ... ))''' auch Integer-Berechnungen ausführen: {{{#!vorlage Befehl pingu@ubuntu:~$ echo $((100 / 3)) 33 pingu@ubuntu:~$ x="62" pingu@ubuntu:~$ echo $(($x + 12)) 74 pingu@ubuntu:~$ ((x++)) pingu@ubuntu:~$ echo "$x" 63 }}} Es gilt hier auch Punkt vor Strich: {{{#!vorlage Befehl pingu@ubuntu:~$ a=$((3 + 4 * 2)) pingu@ubuntu:~$ echo "$a" 11 pingu@ubuntu:~$ b=$(((3 + 4) * 2)) pingu@ubuntu:~$ echo "$b" 14 }}} Eine Aufstellung der verfügbaren Rechenarten in ihrer Vorrang-Reihenfolge findet sich im Abschnitt [http://www.gnu.org/software/bash/manual/bashref.html#Shell-Arithmetic Shell Arithmetic] {en} des [http://www.gnu.org/software/bash/manual/bashref.html Bash-Skripting-Guide] {en}. Man kann abhängig vom System durchaus sehr große Zahlen berechnen, irgendwann erfolgt allerdings kommentarlos der Überlauf. Beim 32-bit-Hardy z.B.: {{{#!vorlage Befehl pingu@ubuntu:~$ echo $((10 ** 18)) 1000000000000000000 pingu@ubuntu:~$ echo $((10 ** 19)) -8446744073709551616 }}} Für weitergehende Berechnungen müsste man dann auf die Rechenfunktionen externer Programme zurückgreifen, z.B. auf [:Taschenrechner#bc:bc] oder [:awk:]. == Arrays == Feldvariablen werden englisch als "Arrays" bezeichnet. Sie ermöglichen es, mehrere Werte innerhalb einer Variablen abzuspeichern. Die einzelnen Werte stehen dabei in 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. === Arrays belegen === Um einen Wert in ein Array zu schreiben, ist es nötig, den Index mit anzugeben. Allgemein sieht das folgendermaßen aus: {{{#!vorlage Befehl Arrayname[i]=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 i-te Feld des Arrays „Arrayname“ geschrieben. Soll ein neues Array mit mehreren Werten auf einmal belegt werden, dann bietet sich folgende Schreibweise an: {{{#!vorlage Befehl Arrayname=(Dies sind vier Werte) }}} Die innerhalb der Klammern angegebenen Werte werden dabei implizit bei 0 beginnenden Indizes zugewiesen. Dieses Array enthielte also die vier Werte „Dies“, „sind“, „vier“ und „Werte“, wobei zum Beispiel „sind“ den Index 1 hätte. Arrays können „Löcher“ haben, das heißt, Indizes müssen keine aufeinanderfolgenden Zahlen sein, und es müssen auch nicht alle Indizes ab 0 belegt sein. Es können auch Elemente an das Ende eines Arrays angefügt werden. Das Ende ist dabei der höchste bereits existierende Index: {{{#!vorlage Befehl Arrayname+=(all diese sieben Elemente werden einzeln angefügt) }}} [#Quoting Quoting] ist notwendig, falls Elemente Leer- oder andere Sonderzeichen enthalten sollen. === Arrays auslesen === Arrays lassen sich genau wie die Variablen mit Hilfe des Befehls ``echo`` auslesen. Hierbei sind jedoch verschiedene Möglichkeiten gegeben. Es können gezielt Elemente mit einem bestimmten Index angesprochen werden: {{{#!vorlage Befehl echo ${array[2]} }}} Wird als Index ein „`@`“ oder „`*`“ angegeben, so kann auf das gesamte Array zugegriffen werden. In diesen Fällen gibt es allerdings Besonderheiten bezüglich doppelter Anführungszeichen um das Array herum: 1. `${array[*]}` oder `${array[@]}`: Alle Elemente mit Nachbearbeitung wie Globbing oder Word-Splitting. 1. `"${array[*]}"`: Kein Globbing, Elemente getrennt durch erstes Zeichen aus `$IFS`, Endergebnis ist ''ein'' Argument. 1. `"${array[@]}"`: Kein Globbing, jedes Element wird ein eigenes Argument. Hierzu einige verdeutlichende Beispiele. Angenommen, das Array wurde mit den folgenden drei Elementen belegt: {{{#!vorlage befehl array=('Hallo, Welt!' '*' ' Noch ein paar Worte und Leerzeichen.') }}} Im Fall 1 expandiert das Element, das nur den Stern enthält, zu allen Dateien im aktuellen Verzeichnis, ebenso werden Worte an Leerzeichen getrennt: {{{#!vorlage befehl echo ${array[*]} }}} {{{ [mark]Hallo,[/mark] [mark]Welt![/mark] [mark]bin[/mark] [mark]boot[/mark] [mark]dev[/mark] [mark]etc[/mark] [mark]home[/mark] [mark]lib[/mark] [mark]lost+found[/mark] [mark]media[/mark] [mark]mnt[/mark] [mark]opt[/mark] [mark]proc[/mark] [mark]root[/mark] [mark]run[/mark] [mark]sbin[/mark] [mark]srv[/mark] [mark]sys[/mark] [mark]tmp[/mark] [mark]usr[/mark] [mark]var[/mark] [mark]Noch[/mark] [mark]ein[/mark] [mark]paar[/mark] [mark]Worte[/mark] [mark]und[/mark] [mark]Leerzeichen.[/mark] }}} Die gelben Markierungen sollen zeigen, dass die resultierende Ausgabe keine zusammengehörige Zeichenkette ist, sondern aus mehreren Argumenten besteht. Ebenfalls ist zu beachten, dass die zusätzlichen Leerzeichen im dritten Element weggefallen sind. Im Fall 2 hingegen entsteht ein einziges Argument, es findet keine Expansion statt, und die Leerzeichen bleiben erhalten: {{{#!vorlage befehl echo "${array[*]}" }}} {{{ [mark]Hallo, Welt! * Noch ein paar Worte und Leerzeichen.[/mark] }}} Als „Verbindungsstück“ zwischen den Elementen wird hier das erste Zeichen der Variablen `$IFS` verwendet. Belegt man diese Variable um, so kann man auch das Verbindungsstück ändern: {{{#!vorlage befehl IFS=';' echo "${array[*]}" }}} {{{ Hallo, Welt![mark];[/mark]*[mark];[/mark] Noch ein paar Worte und Leerzeichen. }}} Das Ergebnis ist immernoch ''ein'' Argument, doch zwischen den Elementen steht jetzt kein Leerzeichen mehr, sondern ein Semikolon. {{{#!vorlage hinweis Die Variable `$IFS` erfüllt noch weitere Zwecke, zum Beispiel wird ihr Inhalt beim Wordsplitting verwendet. Sie sollte nur mit Vorsicht umbelegt werden. Im Zweifelsfalle sollte man sich den Inhalt vorher in eine andere Variable sichern und diesen Wert später wiederherstellen. }}} Fall 3 stellt den reinen Zugriff auf das Array dar. Änderungen an der Variablen `$IFS` spielen keine Rolle mehr, da ''getrennte'' Argumente entstehen: {{{#!vorlage befehl echo "${array[@]}" }}} {{{ [mark]Hallo, Welt![/mark] [mark]*[/mark] [mark] Noch ein paar Worte und Leerzeichen.[/mark] }}} Übung für den Leser: Verwende testweise `ls` statt `echo` und existierende Dateinamen als Arrayinhalt. Das sollte noch einmal herausstellen, was der Unterschied zwischen einem und mehreren Argumenten ist. Wer sich nur die Anzahl der in einem Array enthaltenen Elemente ausgeben lassen will, kann dies mit dem folgenden Befehl tun: {{{#!vorlage befehl echo ${#array[@]} }}} {{{ 3 }}} === Arrays löschen === Arrays lassen sich genau wie Variablen mit dem Befehl `unset` löschen. {{{#!vorlage Befehl unset array }}} === Beispiel für ein Array === Das folgende Beispiel zeigt etwas ausführlicher den Umgang mit einem Array. {{{#!vorlage Befehl array[1]=Hallo echo ${array[@]} }}} {{{ Hallo }}} {{{#!vorlage Befehl array[2]=Pingu echo ${array[@]} }}} {{{ Hallo Pingu }}} {{{#!vorlage Befehl array[1]=Bye echo ${array[@]} }}} {{{ Bye Pingu }}} {{{#!vorlage Befehl 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. [[Bild(Wiki/Icons/users.png, 75, right)]] == Interaktion mit dem Benutzer == 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, widmet sich der folgende Abschnitt. === Ausgabe von Text ==== `echo` kann man verwenden, um den Benutzer des Skriptes wissen zu lassen, was gerade passiert, oder um ihn aufzufordern, eine Eingabe zu machen. {{{#!code bash #!/bin/bash #Hallo Welt Skript echo "Hallo Welt" }}} Die Shebang wurde bereits beschrieben, ebenso Kommentare. Mit `echo` gibt man Nachrichten auf der Standardausgabe aus, welche somit also am Bildschirm angezeigt werden. Dieses Beispiel würde folgende Ausgabe generieren. {{{ Hallo Welt }}} === Einlesen von Eingaben === Mit `read` kann man Eingaben von der Standardeingabe einlesen und somit Benutzereingaben verarbeiten. {{{#!code bash #!/bin/bash #Begrüßung read -p "Geben sie ihren Namen ein:" name echo Hallo: $name }}} In diesem Beispiel fordert man den Benutzer des Skriptes dazu auf, eine Eingabe zu machen. `read` liest die Eingabe in die hinter `read` stehende Variable `$name` ein. Der Schalter `-p` legt einen Prompt fest, der angezeigt wird. Die Ausgabe des Skriptes sieht dann wie folgt aus: {{{#!vorlage Befehl ./begruessung.sh }}} {{{ Geben sie ihren Namen ein: Sab Hallo: Sab. }}} Man kann das auch in eine einzige Zeile packen, falls man die Kommandos direkt auf der Kommandozeile verwenden muss. Ein Beispiel: {{{ read -p "Programm A (a) oder B (b) starten? Geben Sie a oder b ein und die Eingabetaste, Abbruch mit jeder anderen Taste ... " kommando; if [ $kommando == 'a' ]; then starte_programm_a; elif [ $kommando == 'b' ]; then starte_programm_b; else echo "Abbruch."; fi }}} Hier wird der Benutzer aufgefordert a oder b einzugeben, dann wird das jeweilige Programm ausgeführt. Die Eingabeaufforderung und die if-Verzweigung werden in einer Zeile abgesetzt. Man muss zwischen die einzelnen Befehle hier dann immer einen ";" setzen, um sie zu trennen. == Umleitungen == 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 [:Shell/Umleitungen: Umleitungen]. ||<-4 cellstyle="background-color: #E2C890;" :> '''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 Pipeoperator lässt sich die Ausgabe eines Kommandos als Eingabe für ein anderes verwenden. || || `<` || Statt von der Standardeingabe (Tastatur) wird aus einer Datei gelesen || == Das Test-Kommando == Das Testkommando wird benötigt, um diverse Bedingungen 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 aufgelistet. ||<-4 cellstyle="background-color: #E2C890;" :> '''Die Optionen des Test-Kommandos''' || || '''Operator''' || '''Erklärung''' || || ``-d Verzeichnis`` || Ist wahr, wenn "Verzeichnis" existiert. || || ``-e Datei`` || Ist wahr, wenn Datei existiert. || ||``-f Datei`` || Ist wahr, wenn reguläre 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) || || ``! foo`` || Ist wahr, wenn foo falsch ist, also eine Negation.|| Strings und Stringvariablen sollten stets gequotet werden, sonst kann es zu unerwarteten Ergebnissen führen (beispielsweise werden unmaskierte, leere Variablen mit dem Ausdruck `-n` nicht erkannt). Wie man das Testkommando nun richtig anwendet, wird gleich im Abschnitt Programmführung noch gezeigt. == Programmführung == 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. === If-Else-Anweisung === Die Verzweigung macht es möglich, nach einem Test auf eine Bedingung hin eine entsprechende Maßnahme zu ergreifen. Das Konstrukt ist folgendermaßen aufgebaut. {{{#!code bash if [ Test-Bedingung ] then Befehl... fi }}} In den eckigen Klammern kann man jetzt jede der zuvor kennengelernten Option des Testbefehls benutzen. Der Anweisungsblock wird mit `fi` geschlossen. Man kann dieses Konstrukt auch noch etwas erweitern, um eine alternative Reaktion auf einen Test hinzuzufügen. {{{#!code bash 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 üblicherweise folgendermaßen aus. {{{#!code bash if [ Test-Bedingung ] then Befehl... elif [ Test-Bedingung ] then Befehl... else Befehl... fi }}} Ein Beispiel zeigt, wie man zwei Zeichenketten miteinander vergleicht und dem Ergebnis entsprechend eine Meldung dazu ausgibt. Wichtig zu beachten sind hier die Leerzeichen vor und hinter dem Vergleichsoperator. {{{#!code bash #!/bin/bash echo "Wie ist Ihr Name?" read ANTWORT if [ "$ANTWORT" == "root" ] then echo "Hallo, Administrator." else echo "Hallo, Anwender." fi }}} Ein Beispiel zeigt, wie man auf die Existenz einer Datei hin prüft und entsprechend dem Ergebnisse eine Meldung dazu ausgibt. {{{#!code bash #!/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 wird mit einer `elif`-Konstruktion erweitert. {{{#!code bash #!/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 }}} === Case-Anweisungen === Die case-Anweisung ist ein wichtiges Konstrukt, um nach dem Test einer Variable entsprechend auf deren Inhalt zu reagieren. Sie ist auch eine elegante Alternative zu verschachtelten If-Else-Konstrukten, mit welchen prinzipiell dasselbe Ergebnis erzielt wird. Üblicherweise sieht eine `case`-Anweisung folgendermaßen aus. {{{#!code bash case "$variable" in wert1) Befehl... ;; wert2) Befehl... Befehl... Befehl... ;; wert3) Befehl... Befehl... ;; wert4) Befehl... ;; *) Befehl... Befehl... ;; esac }}} Hier wird die Variable `variable` auf verschiedene Werte überprüft, welche mit `wert1` bis `wert4` definiert werden. Andere als die vorgegebenen Muster für den Variablenwert können in dem speziellen Abschnitt `*)` abgefangen werden. Jeder Abschitt wird durch ein doppeltes Semikolon `;;` beendet. Hat ein Abschnitt kein doppeltes Semikolon, wird nach seiner Abarbeitung der unmittelbar folgende Abschnitt ebenfalls abgearbeitet. Das ist im Normalfall jedoch nicht gewünscht. Ein Beispiel zeigt dies nochmal etwas deutlicher. {{{#!code bash #!/bin/bash # namenstest read -p "Bitte geben sie ihren Namen ein:" name case "$name" in Frank) echo Hallo, Frank ;; Werner) echo Hallo, Werner ;; "Mario Kevin Fritz") echo Hallo, Mario Kevin Fritz ;; *) echo "Hallo, Fremder!" ;; esac }}} Es lassen sich auch Alternativen für eine Testbedingung angeben. Dazu wird der Pipe-Operator "|" verwendet, was folgendermaßen aussehen könnte: {{{#!code bash #!/bin/bash # installer read -p "Installer: Adminrechte werden benötigt. Wollen Sie fortfahren?:" 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 }}} Der erste Eintrag wird ausgeführt, wenn `Yes, yes, Y, y` oder eine leere Zeichenkette, der zwei aufeinanderfolgenden Anführungszeichen entspricht, eingegeben wurde. Die leere Zeichenkette definiert eine Art Standardeingabe, die angenommen wird, wenn nur die Eingabetaste betätigt wird, ohne vorher eine andere Eingabe zu tätigen. === Schleifen === 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, im Folgenden wird jedoch nur auf die `for`- und `while`-Schleife näher eingegangen. * `for`-Schleife * `while`-Schleife * `until`-Schleife Die Syntax für die `for`-Schleife sieht folgendermaßen aus. {{{#!code bash 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. {{{#!code bash #!/bin/bash # for-schleife for wort in "Hallo" "du" "Welt" "da" "draußen" do echo "$wort" done }}} Dieses Beispiel erzeugt die folgenden Ausgabe: {{{#!vorlage Befehl ./forschleife }}} {{{ Hallo du Welt da draußen }}} Der nachfolgende Code bezieht sich noch einmal auf das Beispiel, bei dem eine Variable mit Hilfe der Ausgabe eines Befehles gefüllt wurde. Dies kann sehr gut mit einer `for`-Schleife verbunden werden: {{{#!code bash #!/bin/bash # Variablendefinition inputdir="/home/foo/Bilder/ZuBearbeiten" outputdir="/home/foo/Bilder/Bearbeitet" # Abarbeiten der eingelesenen Bilder mit Hilfe einer For-Schleife # und dem Programm ImageMagick. for pic in "$inputdir"/*.png ; do picname=$(basename "$pic") echo "Bearbeite Bild: $picname" convert "$pic" -colorspace Gray "$outputdir/$picname" done }}} Dieses Skript schreibt zuerst einmal alle Namen der '''png'''-Bilddateien im Ordner '''inputdir''' in die Variable `pic`. 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. Die Schleife endet nach der Bearbeitung des letzten Listeneintrages. Diese Methode lässt sich auch auf Arrays übertragen, jedoch sieht das Konstrukt etwas anders aus. Hier zunächst in allgemeiner Form: {{{#!code bash for wert in "${array[@]}" do Befehl... done }}} Mit der `for`-Schleife lassen sich auch Berechnungen durchführen. Dies ist praktisch, wenn die Schleife eine festgelegte Anzahl von Durchgängen durchlaufen soll oder wenn für einen Befehl eine Zahlenreihe benötigt wird. Die Syntax hierzu lautet: {{{#!code bash for ((Anfangswert;Bedingung;Operation)) do Befehl1 Befehl2 usw. done }}} So kann man zum Beispiel Jahresarchivordner für die letzten zehn Jahre in einem Rutsch erstellen: {{{#!code bash for ((z=2004;z<=2014;z++)) do mkdir Archiv_"$z" done }}} Für die Zahlenreihen kann man auch größere Abstände wählen. Will man für die Zahlenreihe Fünferschritte haben, dann muss die erste Zeile zum Beispiel {{{#!code bash for ((z=5;z<=50;z+=5)) }}} lauten. Die `while`-Schleife sieht allgemein folgendermaßen aus. {{{#!code bash while [ Test-Bedingung ] do Befehl1 Befehl2 usw. done }}} Die `while`-Schleife wird solange ausgeführt, wie eine Bedingung zutrifft. Im nachfolgenden Beispiel wartet eine `while`-Schleife, bis eine bestimmte Benutzereingabe stattgefunden hat. Das geschieht durch eine Kombination einer Endlosschleife (`while true`) mit einer `case`-Anweisung, die ein `break` enthält. {{{#!code bash #!/bin/bash # Warten auf das richtige Ergebnis. echo "Frage: Was ergibt 2 + 2" echo "" while true do read -p "Bitte ihre Eingabe:" answer case "$answer" in 4) echo richtig break ;; *) echo falsch ;; esac done }}} === Parameter an ein Skript übergeben === Ein Skript kann bei seinem Start Parameter übernehmen, indem diese, durch Leerzeichen oder Tabulator (engl.: "whitespaces") getrennt, bei seinem Aufruf übergeben werden. Die Parameter befinden sich zur Laufzeit des Skripts in den Variablen $1 bis $9 und können dazu genutzt werden, das Skript in seiner Arbeit zu beeinflussen. Ein Beispiel soll hier zeigen, wie dies generell funktioniert. {{{#!code bash #!/bin/bash # willkommen if [ $1 == "Pingu" ] then echo "Hallo, kleiner Pingu!" else echo "Hallo, $1" fi }}} Und jetzt der Aufruf des Skriptes: {{{#!vorlage Befehl ./willkommen.sh Pingu }}} {{{ Hallo, kleiner Pingu! }}} {{{#!vorlage Befehl ./willkommen.sh Peter }}} {{{ Hallo, Peter }}} Mit Hilfe des Befehls `shift` kann das Skript auf mehr als neun Parameter zugreifen. Shift verschiebt dabei alle Parameter, die auf der Kommandozeile übergeben wurden, eine Position nach links. In dem kurzen Beispiel werden kurzschließende Tests durchgeführt, die im Prinzip wie `if` arbeiten. {{{#!code bash #!/bin/bash while [ "$1" != '' ] do [ $1 == "-b" ] && BACKUP="yes" && echo "Ok. Do a backup!" && shift [ $1 == "-r" ] && RESTORE="yes" && echo "Ok. Do a restore!" && shift [ $1 == "-c" ] && CLEAN="yes" && echo "Ok. Tidy up!" && shift done }}} Die Bedingung, mit der die `while`-Schleife initiiert wird, bedeutet soviel wie: Fahre fort, solange "$1" nicht leer ist. Werden nun mehrere Parameter an das Skript übergeben, so wird immer der an Position eins liegende und in "$1" gespeicherte bearbeitet. Ist dies geschehen, wird durch `shift` der Parameter, welcher sich an der zweiten Position befindet und in "$2" gespeichert ist, an die erste Position gerückt, und die Schleife beginnt von vorne. Mehrere Befehle können durch doppelte Und-Zeichen "&&" zu kurzschließendenn Tests verbunden werden, d.h. bei mehreren, mit && verbundenen Tests wird die Auswertung abgebrochen, sobald ein Teilausdruck falsch ist, weil dann der Gesamtausdruck falsch ist; umgekehrt wird bei mehreren veroderten Tests (||) abgebrochen, sobald einer ''wahr'' ergibt, da dann der Gesamtausdruck ''wahr'' ergibt. Dies kann Zeit sparen, wenn spätere Tests ignoriert werden können, aber mit Überraschungen verbunden sein, wenn man nicht berücksichtigt, dass spätere Tests Nebenwirkungen (engl. "side effects") haben sollen, die dann auch übersprungen werden. In diesem Zusammenhang ist die Option `extglob` interessant, mit der die übergebenen Parameter komplexen Tests unterzogen werden können. Einfacher, schneller und komfortabler können Parameter mit dem Befehl `getopts` ausgewertet werden. Ein Beispiel soll zeigen, wie `getopts` arbeitet. {{{#!code bash #!/bin/bash # Getopttest while getopts ':brcp:' OPTION ; do case "$OPTION" in b) echo "Ok, do a backup";; p) echo "Backup path is: $OPTARG";; r) echo "Ok, do a restore";; c) echo "Ok, tidy up afterwards";; *) echo "Unknown parameter" esac done }}} Der Befehl `getopts` arbeitet mit bereits vertrauten Konstrukten. Es kommen eine `while`-Schleife und eine `case`-Anweisung zum Einsatz. Zu beachten ist die spezielle Notation in der Parameterliste von `getopts`: Der erste Doppelpunkt ist optional und schaltet die Anzeige von Fehlermeldungen ab. Dahinter stehen die Parameter als aufeinanderfolgende Buchstaben. Folgt einem Parameter ein Doppelpunkt, bedeutet dies, dass er ein Argument benötigt. Im oben dargestellten Beispiel ist das bei `p` der Fall. Der Parameter `p` nimmt als Argument einen Pfad auf. Auf diesen kann über die Variable "${OPTARG}" zugegriffen werden. === Funktionen === Funktionen helfen, die Übersichtlichkeit zu verbessern. Sie können mehrere Befehle zu semantischen Gruppen zusammenfassen. Wurden sie einmal definiert, können sie im Skript durch ihren Namen aufgerufen werden. Wichtig ist, dass die Definition der Funktion vor ihrem ersten Aufruf stattgefunden haben muss. Es ist also ratsam, zunächst die Funktionen zu definieren: {{{#!code bash funktionsname(){ Befehle... } }}} Ein Beispiel zeigt eine konkrete Funktion. {{{#!code bash #!/bin/bash PFAD="/home/pingu/" # Funktionsdefinitionen wievieledateien() { anzahl=0 for file in "$PFAD"/* ; do (( anzahl++ )) done echo "Es befinden sich $anzahl Dateien und Ordner in $PFAD" } # Hauptteil wievieledateien }}} Das Skript liefert nach seinem Aufruf: {{{#!vorlage Befehl ./wievieledateien }}} folgende Ausgabe: {{{ Es befinden sich 90 Dateien und Ordner in /home/pingu/ }}} Die Kommentare `#Hauptteil, #Funktionsdefinitionen, ` usw. macht man in richtigen Skripten nicht - sie dienen nur hier der Verdeutlichung für Anfänger. Nicht nur an das Skript, sondern auch an Funktionen können Parameter übergeben werden. Dies funktioniert wie die Übergabe von Skriptparametern, indem die Parameter an den Funktionsaufruf, zumeist durch Leerzeichen voneinander getrennt, angehängt werden. Das folgende Beispiel ergänzt das vorhergehende um Funktionsparameter. Wichtig ist auch, dass die an das Skript übergebenen Parameter in den Funktionen nicht "direkt" in Form `${X}` verwendet werden können, sondern "neu definiert" werden müssen (hier `zaehlpfad=${1}`). Das kann vor Definition der Funktion oder auch erst in der Funktion selbst erfolgen. {{{#!code bash #!/bin/bash wievieledateien() { zaehlpfad="$1" for file in "$zaehlpfad"/* ; do (( anzahl++ )) done echo "Es befinden sich $anzahl Dateien und Ordner in $zaehlpfad" } wievieledateien /home/pingu wievieledateien /home/edit }}} == Grafische Menüs == Ein Skript kann ein Hilfsprogramm nutzen, um GUI-Elemente wie Auswahllisten, Datei-öffnen-Dialoge, Auswahlbuttons usw. zu erzeugen. Entweder entscheidet man sich für ein spezielles Hilfsprogramm, das günstigerweise automatisch mit einer Oberfläche installiert wird (z.B. GNOME: [:Zenity:], KDE: [:KDialog:], Textkonsole: dialog, etc.), oder man nutzt ein universelles Hilfsskript wie [http://sites.google.com/site/easybashgui/ easybashgui] {en}, das automatisch das jeweils passende, installierte Text- oder Grafik-Hilfsprogramm nutzt. Der folgende Abschnitt soll erklären wie die Erstellung grafischer Dialoge mit Bash-Skripten prinzipiell funktioniert. Hauptsächlich wird dabei auf das Programm dialog (textbasiert) eingegangen. === Menüs mit Dialog === [:Dialog:] bietet eine 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. Ein einfaches Beispiel soll die Arbeit mit Dialog illustrieren. {{{#!code bash #!/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 Schaltflächen 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 stehen die Rückgabewerte `0` für ja, `1` für nein und `255` für Abbruch ohne Auswahl. Im nächsten Schritt soll dieses Beispiel ausgebaut werden. {{{#!code bash #!/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 habe ich mir fast schon gedacht!" 15 40 fi }}} Im Beispiel wurden zusätzliche Optionen eingesetzt. So setzt `--backtitle` den Titel des Hintergrundes, `--title` setzt die Überschrift der Box. Zur `--yesno`-Box kommen zwei Messageboxen (`--msgbox`) hinzu, die nur eine Nachricht darstellen und dann über einen OK-Button geschlossen werden können. Diese Messageboxen erscheinen in Abhängigkeit von der Antwort, die im ersten Fenster gegeben wurde. Dieser Dialog soll nun weiter verfeinert werden. {{{#!code bash #!/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 Desktop-Umgebung 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 positiven Antwort noch etwas weiter. Mit Hilfe einer Radioliste `--radiolist` wird die bevorzugte Desktopumgebung erfragt. Eine Radioliste zeichnet sich dadurch aus, dass nur ein Eintrag aktiv sein kann. Sollen mehrere aktive Einträge erlaubt sein, sollte stattdessen `--checklist` verwendet werden. Die Antwort aus der Radioliste wird in eine Variable geschrieben, um sie danach weiterverarbeiten zu können. Dies geschieht durch eine `case`-Anweisung. Wichtig: Die Option `--stdout` darf beim Schreiben in eine Variable nicht vergessen werden, sie sorgt für eine Ausgabe auf dem Bildschirm. Wäre sie nicht gesetzt, würde der Dialog nicht erscheinen. = Literaturhinweise = * [http://openbook.rheinwerk-verlag.de/shell_programmierung/ Shell-Programmierung – Einführung, Praxis, Referenz] {de} - von Jürgen Wolf, Rheinwerk = Links = * [:Shell:] {Übersicht} Hauptartikel zur Shell mit vielen weiteren Links (intern + extern) * [:Bash:] - spezielles zur '''B'''ourne '''a'''gain '''sh'''ell * [:Shell/Tipps_und_Tricks: Tipps & Tricks], um Fehler zu vermeiden * extern * Shellscripts: http://www.bin-bash.de/scripts.php #tag:Shell, Einsteiger, Programmierung