[[Vorlage(Überarbeitung, 30.04.2011, Shell/Bash-Skripting-Guide_für_Anfänger, Vain user_unknown)]] [[Vorlage(Getestet, general)]] {{{#!vorlage Wissen [:Terminal: Ein Terminal öffnen] [:Shell: Grundsätzlicher Umgang mit der Shell] [:Programme starten: Ein Programm starten] }}} [[Inhaltsverzeichnis(2)]] 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. == Einsatzgebiete von Bash-Skripten == 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 [:Skripte: Beispiele], welche die breite Anwendbarkeit von Bash-Skripten ein wenig illustrieren. = Vorbereitung = == Editor == Zuerst sollte man einen passenden 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:] Wer mehr auf Konsolenfeeling steht, kann sich mal diese Editoren ansehen: * [:Vim:] (Nicht leicht für Anfänger, aber trotzdem sehr empfehlenswert) * [:Nano:] Eine Übersicht weiterer Editoren findet sich im Artikel [:Editoren:]. {{{#!vorlage Hinweis 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. }}} == 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 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. == Ausführbar machen und aufrufen == 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. {{{#!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]. {{{#!vorlage Hinweis Man kann das Skript, nachdem es ausführbar gemacht wurde, in einen Ordner (z.B. '''~/bin''') verlinken oder kopieren, 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. 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. }}} == 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, 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 nur indirekt über seinen Interpreter gestartet. Das Skript selbst erzeugt keinen [wikipedia:Prozess_(Informatik):Prozess], sondern nur der ausführender 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:] von Hand festzulegen, doch diesen Aufwand möchte man nicht wirklich treiben. Die übliche Vorgehensweise ist, dies direkt im Skript anzugeben. Dies wird durch die Shebang ermöglicht, welche die erste Zeile eines Skriptes darstellt. 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 die 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 }}} In der 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. Die Angabe kann hier nach Wunsch variieren und eventuell auch eine andere Shell beinhalten. Hierbei sollte man aber bedenken, dass man sich dann genauer mit der Syntax dieser auseinandersetzen sollte. Dieser Artikel hingegen bezieht sich auf die Bash und richtet sich an Einsteiger unter den Ubuntu-Nutzern, weshalb '''/bin/bash''' verwendet werden soll. 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 dem [wikipedia:POSIX:POSIX-Standard] ein Muss. Hinsichtlich des Funktionsumfangs stimmen die von POSIX geforderte sh und die Bash aber keinesfalls ü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: Die Shebang muss genau einen solchen Interpreter angeben, welcher 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. Dies ist sehr wichtig und eine falsch gewählte Shebang ist leider ein häufiger Fehler. Ein Bash-Skript darf also niemals '''/bin/sh''' als Shebang verwenden – dies funktioniert lediglich in Ausnahmefällen. {{{#!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. 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 – Teil 1 == 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 belegen === Variablen werden folgendermaßen belegt: {{{#!vorlage Befehl 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 auslesen === 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: {{{#!vorlage Befehl echo "$var" }}} {{{ hallo }}} === 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 `var` weiterverfolgt. {{{#!vorlage Befehl echo "$var" }}} {{{ hallo }}} {{{#!vorlage Befehl unset var echo "$var" }}} {{{ }}} === Variablen abgrenzen === 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. {{{#!vorlage Befehl echo "$var" }}} {{{ hallo }}} {{{#!vorlage Befehl 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. {{{#!vorlage Befehl 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. {{{#!vorlage Hinweis Oft wird diese Schreibweise gegenüber der ohne Klammern bevorzugt. Ich werde dies in diesem Artikel ab hier auch so handhaben. }}} == Quoting == === 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 „Wort“-Trenner. Ein oder mehrere aufeinanderfolgende 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 muss `ls` dies nicht selbst 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 nur für einen einzigen Befehl gelten können – 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 [:Locale:] benutzt wird und die Ausgabe in einer anderen Sprache erfolgt. 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. Heutzutage sind in Dateinamen beinahe alle Zeichenfolgen erlaubt, insbesondere also auch solche, die von der Shell ausgewertet werden würden. Dies könnte ungewünschte Folgen haben. Insbesondere gilt dies natürlich für Dateinamen, die Leerzeichen enthalten. }}} === Maskierung mit Backslashes === Eine Alternative zum Setzen von Anführungszeichen stellt der 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. }}} == Variablen – Teil 2 == === Ausgaben in eine Variable schreiben === Durch eine Befehlssubstitution ist es möglich, die Ausgaben 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` zurückgeliefert. 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 Font, schlecht vom Apostroph zu unterscheiden sind. }}} Es ist eine gute Gewohnheit, Variablen ''immer'' in Gänsefüßchen einzuschließen, auch wenn man das bei ''echo'' nicht grundsätzlich braucht. Dabei muss man zwischen den einfachen und den doppelten Gänsfüßchen unterscheiden. Bei "..." werden z.B. Variablen expandiert, bei '...' nicht. {{{#!vorlage Warnung Es ist verführerisch, auf diese Weise auch die Ausgabe des Befehls `ls` im Skript zu verarbeiten. Das wäre aber gleich in mehrerer Hinsicht gefährlich: * In Dateinamen dürfen Zeilenwechsel vorkommen. In dem Fall gerät die Zeilenzuordnung bei `ls` völlig durcheinander. * 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". * Je nach Einstellung fügt `ls` auch noch zusätzlich Farbcodes in seine Ausgabe ein. Dadurch bekäme man schnell ein völlig unerwartetes Ergebnis, das auch noch je nach Systemumgebung variiert. Auf jeden Fall wäre dies kein zuverlässiger Skriptcode. Im ungünstigsten Fall schreibt man sogar eine Softwarebombe, ohne dass man es merkt. Daher sollte man sich von Anfang an einen ordentlichen Programmierstil angewöhnen, ohne solche Fallstricke. }}} Dateien und Verzeichnise lassen sich gefahrlos mit den ''bash''-eigenen Funktionen abfragen: {{{#!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 dem String 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 möchte ich hier kurz eine Anwendungsmöglichkeit erläutern. {{{#!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 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. ||<-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-Berechungen 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] des [http://www.gnu.org/software/bash/manual/bashref.html Bash-Skripting-Guide]. 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 weiter gehende Berechnungen müsste man dann auf die Rechenfunktionen externer Programme zurückgreifen, z.B. auf '''bc''' (siehe [http://www.manpagez.com/man/1/bc/ man bc]) oder '''awk''' (siehe [http://www-e.uni-magdeburg.de/urzs/awk/ Einführung awk]). == Arrays == Arrays werden auch als Feldvariablen bezeichnet. Sie ermöglichen es, mehrere Werte gleicher Art innerhalb einer Variable 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[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 n-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. Dieser Befehl ermöglicht den Zugriff auf das komplette Array. {{{#!vorlage Befehl echo ${array[@]} }}} Durch das At-Zeichen in den Klammern für den Index erreicht man die Ausgabe aller Elemente des Arrays. Zudem gibt es auch die Möglichkeit auf einzelne Elemente zuzugreifen. {{{#!vorlage Befehl echo ${array[2]} }}} Dabei wird einfach über die Angabe des Index das entsprechende Element ausgegeben. === 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 wird sich der folgende Abschnitt widmen. === 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. Hierbei sollte man darauf achten, den auszugebenden Text in Anführungszeichen zu setzen. {{{#!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 ist man in der Lage Eingaben von der Standardeingabe einzulesen, und somit kann man also Benutzereingaben verarbeiten. {{{#!code bash #!/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: {{{#!vorlage Befehl ./begruessung.sh }}} {{{ Geben sie ihren Namen ein: Sab Hallo: Sab. }}} == 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 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. ||<-4 cellstyle="background-color: #E2C890;" :> '''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. == 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 === 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. {{{#!code bash 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. {{{#!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 im Allgemeinen folgendermaßen aus. {{{#!code bash 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. {{{#!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 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. Im Allgemeinen 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 für gewöhnlich 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 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, was folgendermaßen aussehen könnte: {{{#!code bash #!/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 }}} Der erste Eintrag wird ausgeführt, wenn Yes, yes, Y, y oder ein Leerstring, der zwei aufeinanderfolgenden Anführungszeichen entspricht, eingegeben wurde. Der Leerstring 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, 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. {{{#!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" "draussen" do echo "$wort" done }}} Dieses Beispiel erzeugt den folgenden Output. {{{#!vorlage Befehl ./forschleife }}} {{{ Hallo du Welt da draussen }}} 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 Tool ImageMagick. for pic in "$inputdir" ; do picname=$( basename "$pic") echo "Bearbeite Bild: $picname" echo "-------------------------------------------0" convert "$pic" -colorspace Gray "$outputdir$picname" done }}} Dieses Skript schreibt zuerst einmal alle Namen unserer 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 }}} Die While-Schleife sieht im Allgemeinen folgendermaßen aus. {{{#!code bash while [ Test-Bedingung ] do Befehl1 Befehl2 usw... done }}} Die While-Schleife wird solange ausgeführt, wie eine Bedingung zutrifft oder eben nicht zutrifft. Im nachfolgenden Beispiel wartet eine While-Schleife, bis eine bestimmte Benutzereingabe stattgefunden hat. Das geschieht durch eine Kombination mit einer Case-Anweisung. {{{#!code bash #!/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 der Befehl "break". Ist die Antwort richtig, wird die Schleife beendet, andernfalls läuft sie immer weiter. Natürlich kann diese Schleife auch mit jeder anderen Test-Bedingung initialisiert werden. === Parameter an ein Skript übergeben === Ein Skript kann bei seinem Start Parameter übernehmen, indem diese, zumeist durch Leerzeichen 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. 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. 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, das spätere Tests Seiteneffekte 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 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 }}} 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. Besitzt ein Parameter einen Doppelpunkt hinter sich, 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 logischen 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 Funktionen zu definieren und diese später, im Hauptteil des Skripts, aufzurufen. Durch diese Vorgehensweise bleibt das Skript logisch und übersichtlich. Im Allgemeinen sieht eine Funktion folgendermaßen aus. {{{#!code bash funktionsname(){ Befehle... } }}} Ein Beispiel zeigt eine konkrete Funktion. {{{#!code bash #!/bin/bash # Variablendefinition 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 folgende Ausgabe. {{{#!vorlage Befehl ./wievieledateien }}} {{{ Es befinden sich 90 Dateien und Ordner in /home/pingu/ }}} 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, anhängt werden. Das folgende Beispiel ergänzt das vorhergehende um Funktionsparameter. {{{#!code bash #!/bin/bash # Funktionsdefinitionen wievieledateien(){ # Substitution der Parametervariablen zaehlpfad="$1" for file in "$zaehlpfad"/* ; do (( anzahl++ )) done echo "Es befinden sich $anzahl Dateien und Ordner in $zaehlpfad" } # Hauptteil wievieledateien /home/pingu/ }}} == Grafische Menüs == Ein Skript kann durch zusätzliche Programme grafisch aufgewertet werden. Dafür gibt es verschiedene Möglichkeiten. Wie das funktioniert, soll der folgende Abschnitt erklären. Hauptsächlich soll dabei auf das Programm "Dialog" eingegangen werden. === Menüs mit Dialog === 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. Dialog ist standardmäßig nicht installiert, der folgende Befehl holt dies nach. {{{#!vorlage Befehl sudo apt-get install dialog }}} 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 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. 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 hab 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. Die 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 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 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 statdessen "--checkbox" 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. Weitere Möglichkeiten zur Erstellung grafischer Dialoge mittels eines Bash-Skripts bieten die Programme [:Zenity:Zenity] und [:KDialog:KDialog]. Wie diese funktionieren, wird genauer in den jeweiligen Wiki-Artikeln beschrieben. = 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 = Bücherempfehlungen = * [http://bookzilla.de/shop/action/productDetails/6860779/patrick_ditchen_shell_skript_programmierung_3826617991.html Shell Skript Programmierung] {de} - von Patrick Ditchen #tag:Shell, Einsteiger, Programmierung