Einführung: Schnittstelle Kommandozeile Teil 7

von Peter Kelly (critter)

Erstellen von Shell Scripten (scripts)

Ein Script ist ganz einfach eine Liste aufeinander folgender Befehle, die das System ausführen soll und im Normalfall wird das System genau dies tun – gehorsam und blind – ohne Rücksicht auf irgendwelche Auswirkungen. Es ist nicht schwer Scripte zu schreiben, aber man muss aufpassen, dass die Befehle, die man hineinschreibt auch das bewirken, was beabsichtigt war. Dies ist leider nicht immer der Fall.

Es gibt 2 Sorten von Computerprogrammen: kompilierte Programme und Programme, die interpretiert werden. Bei kompilierten Programmen wurde der (Programm)Code in eine Maschinensprache übertragen, die der Prozessor direkt versteht, die für uns Menschen jedoch unverständlich ist. Solche Programme werden extrem schnell ausgeführt, sind aber nur schwer zu verändern. Programme zur Interpretation sind in der Regel reine Textdateien, die vom Interpreter Zeile um Zeile gelesen (und übersetzt) und dann an den Prozessor weitergegeben werden. Shell Scripts stellen interpretierbare Programme dar und bei einem bash Script ist die bash Shell der Interpreter.

Wenn wir mit der Kommandozeile arbeiten, können wir Befehle eingeben und die Shell führt irgendeine Funktion für uns aus. Manchmal geben wir während eines längeren Zeitraums regelmäßig die gleichen Befehle ein und manchmal werden die Befehle auch recht lang und komplex. Manchmal wiederum müssen wir eine ganze Serie von Befehlen eingeben um zum gewünschten Ergebnis zu gelangen. Wenn das hier Gesagte nach dem klingt, womit Sie es zu tun haben, dann ist es Zeit etwas über scripting zu lernen und die Maschine die langweilige Arbeit ausführen zu lassen. Die Scriptsprache der bash Shell ist ein weites Feld und es gibt in den Buchhandlungen viele dicke Bände, die sich damit befassen. Glücklicherweise brauchen Sie nur einen kleinen Teil dieser Zaubereien zu kennen, um ein geübter Scripteschreiber zu werden und um einige der Scripte zu verstehen, die für das Funktionieren und die Kontrolle Ihres Systems verantwortlich sind.

Um ein Script zu schreiben müssen Sie in der Lage sein, Text in eine Datei einzugeben und diese Datei anschließend ausführbar zu machen. Das kann so einfach sein wie das Eingeben von Text auf der Kommandozeile:

cat > myscript
echo Hello
   Geben Sie hier Ctrl+D ein um die Texteingabe zu beenden und schließen Sie die Datei
chmod +x myscript

Wenn Sie jetzt ./myscript eingeben, wird das Script ausgeführt und das Wort Hello wird am Bildschirm ausgegeben. (Die Sequenz ./ braucht man, um der Shell mitzuteilen, wo sich das Script befindet, da es nicht dort steht, wo sich normalerweise ausführbare Dateien befinden).

Die beschriebene Methode funktioniert zwar, aber wenn wir wirklich Scripte schreiben wollen, sollten wir einen Texteditor benutzen. Aber nicht ein Textverarbeitungsprogramm, da diese komplizierte Formatierungssequenzen nutzen, die unsere Shell verwirren würden. Aber jedweder Texteditor ist geeignet. Mein Favorit ist kwrite, aber es gibt noch viele andere. Idealerweise verwenden Sie einen Editor, der Hervorhebung von Syntax unterstützt. Jede Programmiersprache enthält viele Dinge wie z. B. Kommentare, Schlüsselwörter, Variablen, Text usw. Syntaxhervorhebung zeigt diese Dinge in verschiedenen Farben an und erleichtert die Suche nach einer bestimmten Sache.

pic

Wenn Sie einen Terminaleditor verwenden wollen, dann können Sie Nano nehmen, der Syntaxhervorhebung unterstützt. Der ist allerdings standardmäßig ausgeschaltet. Um ihn zu aktivieren müssen Sie eine Datei in ihr Heimatverzeichnis kopieren. Dazu geben Sie in einem Terminal Folgendes ein: cp /usr/share/nano/sh.nanorc ~/.nanorc.

Jede Datei, die Sie jetzt in Nano editieren und deren Dateiname mit .sh endet, wird jetzt mit für die bash Scriptsprache geeigneter Syntaxhervorhebung dargestellt. Andere Dateien sind davon nicht betroffen. Die Erweiterung .sh wird nicht für Scripts benötigt, ist aber ein Weg, Nano mitzuteilen, dass dies 'ein bash Script' ist und daher die Hervorhebung entsprechend angewendet werden soll. Darüber hinaus hilft es Ihre Scripte von anderen Dateien zu unterscheiden. Es ist auch eine gute Idee, ein Verzeichnis für alle Ihre Scripte in Ihrem Heimatverzeichnis einzurichten und diese dort abzulegen. Wenn Sie dann ein Script schreiben, das wirklich hilfreich ist, können Sie es immer noch in das /bin Verzeichnis kopieren, damit es jederzeit verfügbar ist. Dieses Verzeichnis ist fast mit Sicherheit als Teil in Ihrer PATH Variablen eingetragen. Testen Sie es vorher aber gründlich, damit nicht Sie oder irgendein anderer Nutzer des Systems eines Tages eine ärgerliche Überraschung erleben.

pic

Wenn eine ausführbare Datei an die Shell übergeben wird, gibt diese sie an den Kernel weiter, der dann einen neuen Prozess startet und versucht, ihn auszuführen. Wenn es sich nicht um eine kompilierte Datei in Maschinensprache handelt (binary), wird dies fehlschlagen und eine Fehlermeldung an die shell zurückgegeben. Diese sucht dann die Datei nach Befehlen ab, von denen sie weiß, dass sie ausführbar sind. Das kann funktionieren, wie in dem obigen einfachen myscript Beispiel. Aber weil es viele Scriptsprachen gibt, und das daher nicht sicher ist, sollten wir der Shell mitteilen, welchen Interpreter sie benutzen soll. Wenn die allerersten Zeichen der ersten Zeile eines Scripts #! sind (nennt man auch shebang), dann wird die Bash den Rest der Zeile als den voll qualifizierten Pfad (fully qualified path) zum Interpreter ansehen. Ein bash Script sollte daher immer mit

#!/bin/bash, oder mit #!/bin/sh beginnen, wobei /bin/sh normalerweise ein Link zu /bin/bash darstellt. Zum Beispiel würden wir für ein Perl Script #!/usr/bin/perl verwenden.

Was ist nun Inhalt eines Scripts? Nun, schon die Zeile #!/bin/bash stellt technisch gesehen ein Script dar, allerdings macht es nicht viel. Tatsächlich macht es nicht mehr, als einen Prozess zu starten und ihn wieder zu beenden. Um Ergebnisse zu erzielen müssen wir Befehle einfügen, die nacheinander auszuführen sind, oder wir müssen ihr (der shell) mitteilen, die Dinge auf andere Art und Weise auszuführen. Hier können wir zeigen, was wir können. Wir haben die Kontrolle und haben jetzt auch viel mehr Macht als wenn wir nur einfache Befehle in einem Terminal eingeben können. Mit einem Script können wir Befehle abhängig von uns oder vom System festgelegten Bedingungen in der Reihenfolge ausführen lassen, wie wir es wollen und wann wir es wollen und wir können dies regelmäßig ausführen lassen oder solange, bis ein bestimmtes Ereignis (eine Bedingung) eingetreten ist. Beim Start des Scripts können wir ihm Optionen oder Argumente übergeben oder Informationen aus einer Datei oder vom Terminal eines Benutzers einlesen.

Wir können ein einfaches Script schreiben, um ein Verzeichnis für alle bash Scripts zu suchen. Das sähe so aus:

pic

(Vergessen Sie nicht, es mit chmod +x ausführbar zu machen!).

Die Anführungszeichen werden gebraucht, damit das Script nicht alles, was nach dem '#' kommt als Kommentar ansieht. Das zweite Argument /usr/bin/* steht nicht in Anführungszeichen, weil wir wollen, dass die Shell das * in eine Liste aller Dateien erweitert.

All dies könnten wir mit Hilfe der Kommandozeile ausführen, oder wir könnten sogar ein Alias, das wir find-scripts nennen, definieren: alias find-scripts='grep -rs "#!/bin/bash" /usr/bin/*'

Beides wird funktionieren. Das Muster wird jedoch irgendwo in einer Textdatei oder in eine Binärdatei eingebunden gefunden werden und eben nicht nur am Anfang, wo es ein bash Script kennzeichnet. Aber als Beispiel reicht das erst einmal.

Über Variable

Um unsere Bemühungen wertvoller zu machen, können wir unser Script verbessern, indem wir ihm Namen und Pfad für die Scriptsprache auf der Kommandozeile übergeben. Es ähnelt dann schon im Gebrauch einem regulären Linux Befehl.

find-scripts {Suchmuster}

Um dies zu tun, müssen wir variables verwenden. Variable sind uns früher schon begegnet. Es sind die Namen von Informationsstückchen, die wir oder die Shell uns merken müssen wie z. B. PWD, der für den absoluten Pfad des gegenwärtigen Arbeitsverzeichnisses steht oder PATH, der eine in Spalten unterteilte Liste von Verzeichnissen darstellt, die nach ausführbaren Dateien durchsucht wird. Dieses sind Umgebungsvariable, die die Shell benutzt, die aber auch dem Benutzer zugänglich sind. Sie können auch Ihre eigenen Variablen erzeugen. Diese nennt man dann nur Variable. Der Unterschied besteht darin, dass der Inhalt von PWD sich ändert, wenn Sie z. B. mit cd in ein anderes Verzeichnis wechseln: der Inhalt von Umgebungsvariablen ist variabel.

Bei vielen Programmiersprachen ist es notwendig, Variable vor ihrem Gebrauch zu deklarieren und in dieser Deklaration auch den zugewiesenen Inhaltstypus anzugeben. Inhaltstyp kann eine Zeichenkette (string), eine ganze Zahl, eine Fliesskommazahl oder irgend ein anderer der zahlreichen Typen sein. Die Deklaration von Variablen erfolgt innerhalb der Bash durch das Schlüsselwort declare, ist aber in den meisten Fällen nicht notwendig. Sie können in den von Ihnen erzeugten Variablen Zeichenketten oder Ganzzahlen speichern, ganz wie Sie es wollen. Die Bash unterstützt allerdings keine Fliesskommaarithmetik. Wenn diese Funktionalität benötigt wird müssen Hilfsbefehle wie bc eingesetzt werden.

Die Bash unterstützt auch eindimensionale Felder – eindimensional bedeutet, dass Felder von eindimensionalen Feldern nicht (einfach) zugänglich sind. Ein Feld ist einfach eine Gruppe indizierter Variablen mit gleichem Namen mit dem Index Null (0) für das erste Element. In der Bash können Felder sehr flexibel gehandhabt werden. Ein Beispiel: Wenn wir ein Feld mit dem Namen pets erzeugen – pets=(cat dog horse) – dann erhält pet[0] den Wert cat und pet[2] den Wert horse. Wenn wir jetzt pets[4]=parrot hinzufügen, dann wird dieses Element ins Feld eingegliedert, obwohl pets[3] nicht zugewiesen ist.

Um auf den Inhalt eines bestimmten Elements eines Feldes zugreifen zu können, müssen wir den Index in eckige Klammern setzen und geschweifte Klammern verwenden, um Erweiterung durch die Shell zu verhindern:

pic

echo ${pets[1]} gibt richtigerweise dog aus, aber echo $pets[1] gibt cat[1] aus, da die Shell die Variable pets ohne Index auf das erste Element des Feldes (0) erweitert und anschließend der echo Befehl die Zeichenkette [1] wörtlich nimmt und sie zum ausgegebenen Text hinzufügt.

In Scripts werden Felder oft iterativ in einer Schleife abgearbeitet und ihre Werte an das Programm zur Weiterverarbeitung übergeben. Auf diese Weise kann man Sachen wie Listen oder Namen von Musikstücken elegant in einem Script verarbeiten.

Natürlich gibt es auch einige spezielle Wege wie man an Informationen über ein Feld gelangen kann.

pic
echo ${pets[*]}    Gibt alle Elemente des Feldes aus 

echo ${#pets[*]}   Gibt die Anzahl der Elemente eines Feldes aus 

echo ${#pets[2]}   Gibt die Länge des Elements [2] des Feldes aus

echo ${!pets[*]}  Gibt alle Indices der Elemente des Feldes aus. 
Beachten Sie, dass der Index des nicht zugewiesene Feldes 3
nicht erscheint.

Spezielle Variable der Bash

Die Shell verfügt über viele Variablen. Sie nutzt auch einige spezielle Variablen für den Zugriff auf Argumente in einem Script. Das erste Argument hat die Bezeichnung $1, das zweite $2 u.s.w. Im unwahrscheinlichen Fall, sie mehr als 9 Argumente einem Script übergeben müssen muss die Nummer in geschweifte Klammern gesetzt werden, also ${14} für das 14.te Argument. $0 enthält den Befehlsnamen, wie er auf der Kommandozeile verwendet wird.

Die Veränderung des Scripts zu

pic

erlaubt uns, das Script aufzurufen und ihm die absolute Adresse des Interpreters zu übergeben.

./find-scripts /bin/bash um den Ort unserer bash Scripts anzugeben oder ./find-scripts /usr/bin/perl um alle Perl Scripts zu finden. Dabei nutzen wir aus, dass die Shell das erste ihr zu übergebende Argument in der Variablen $1 speichert.

Beachten Sie, dass ich hier die einfachen Anführungszeichen in doppelte verändert habe. Dies erlaubt die Erweiterung der Variablen ($1 wird durch das erste Argument ersetzt), während #! wörtliche Übersetzung bleibt. Hier ist Texthervorhebung unschätzbar. Im ersten Beispiel wird der Befehlsausdruck "#!/bin/bash" in rotem Text dargestellt – der Farbe für Zeichenketten. Im zweiten Beispiel ist #! rot dargestellt, aber $1 grün in der Farbe, die Variable kennzeichnet. Hätte ich einfache Anführungszeichen verwendet, wäre $1 nicht erweitert worden und grep hätte weiter nach Mustern der Zeichenkette #!$1 gesucht.

Das Script können wir noch erweitern, indem wir das zu durchsuchende Verzeichnis als zweites Argument übergeben. Es wird in $2 abgelegt. Jetzt liest sich das Script so:./find-scripts /bin/bash /usr/bin und wird durch die Übernahme von 2 Argumenten viel flexibler.

pic

Bei der Ausführung wird $1 zu /bin/bash erweitert und $2 zu /usr/bin.

Bedauerlicherweise komplizieren diese Verbesserungen das Script, da wir die Argumente nicht übergeben müssen. Wenn wir dem Script nicht die richtige Indizierung der Argumente übergeben, dann bleiben die Variablen $1 und $2 undefiniert. Das heißt, dass dieser Wert nicht von uns kontrolliert wird und Nullwert annimmt. Da wir hier nur Dateien aus einem Verzeichnis einlesen, wird dies nicht viel Schaden anrichten. Wenn das Script allerdings zum Schreiben oder Löschen von Sachen dienen soll, kann man sich die Konsequenzen vorstellen. Mit einem leeren Gewehr kann man sich eine Menge Ärger einhandeln. Ich werde Sie mit einem einfachen Beispiel hiervon überzeugen!

rm -rf /$RETURNED_PATH   Versuchen Sie dieses niemals!
rm remove
-r Recursion durch die Unterverzeichnisse
-f ohne Rückbestätigung oder Anzeige

/$RETURNED_PATH: Wenn diese Variable undefiniert bleibt, dann wird sie zu / erweitert und der Befehl vervollständigt sich dann zu ''alles unterhalb des Wurzelverzeichnisses recursiv ohne Rückfrage löschen“. Das löscht alles im und unterhalb des Wurzelverzeichnis – Ihr System ist weg, endgültig und ohne ''Dankeschön“.

Konditionale Programmierung

Eine weitere besondere Variable ist $#. Sie enthält die Anzahl der Argumente, die an das Script übergeben werden. Das können wir benutzen, um zu prüfen, ob alles in Ordnung ist, bevor wir mit der Ausführung beginnen.

pic

Zur Erklärung habe ich Zeilennummerierung eingeschaltet. Die hat mit dem Script nichts zu tun und sie ist nicht explizit eingegeben worden.

Die Zeilen 3 – 7 beinhalten eine Sequenz, die if-then Schleife genannt wird. Nach dem Schlüsselbegriff if in Zeile 3 wird eine Bedingung auf 'wahr' überprüft. In diesem Beispiel prüft die Testbedingung [ $# !=2 ] ob die Gesamtzahl der übergebenen Argumente nicht gleich 2 ist. Die Leerzeichen innerhalb der Klammer sind sehr wichtig.

Wenn es wahr ist (dass es nicht 2 Argumente gibt) werden die Zeilen 4,5 und 6 ausgeführt. Zeile 4 ist der Eintrittspunkt zu Befehlen, die nur ausgeführt werden, wenn die Testbedingung zutrifft. Zeile 5 gibt über echo eine Ausführungsinformation ans Terminal aus. Mit Zeile 6 wird das Script verlassen, weil zum Weitermachen nicht genügend Information zur Verfügung steht. Dabei wird der Wert 1 zurückgegeben. Mit Zeile 7 wird die bedingte Programmierung beendet und das Script wird normal weiter abgearbeitet.

Bis hierher nützen wir den Rückgabewert 1 nicht. Er bedeutet nach der allgemeinen Konvention ein Fehlschlagen und, 0 bedeutet Erfolg der Operation. Weitere Ganzzahlen bis und einschließlich 125 können für Programmierzwecke verwendet werden. Wenn dieses Script von einem anderen Script aufgerufen wurde, dann würde das Elternscript die Ausgabe dieses Wertes erhalten und dementsprechend reagieren können.

Wenn Sie das Script nach einigen Monaten oder sogar einigen Jahren erneut verwenden, werden Sie sich nicht daran erinnern, über welches Muster oder welches Verzeichnis es sich beklagt. Noch unwahrscheinlicher ist, dass ein anderer Nutzer dies weiß. Deswegen sollten und müssen wir zum Script Kommentare anfügen, die erklären, was sich da abspielt (abspielen soll). Jeglicher Text in einer eigenen Zeile oder am Ende einer Zeile der mit einem # beginnt ist ein Kommentar. (Einzige Ausnahme ist das Eingangs- #! mit seiner besonderen Bedeutung für die Shell). Mit # gekennzeichneter Text wird bei der Ausführung des Scripts ignoriert.

pic

In dieser Datei finden sich mehr Kommentare als üblich, aber eine aussagekräftige Beschreibung kann einem eine Menge Kopfzerbrechen ersparen. Auch Einrücken kann ein Script lesbarer machen.

Der im obigen Beispiel angewandte Test $# != 2, leitet sich vom Negationssymbol ! ab. Zusammen mit dem Gleichheitssymbol = ergibt sich der ''nicht gleich Test''. Aber was ist zu tun, wenn wir wissen wollen, ob eine Datei ein Verzeichnis ist, oder ob sie überhaupt existiert? Nun, dafür hat die Shell ihre ganz eigenen Testbefehle mit nachstehender grundlegender Syntax: test {expression1} {condition} {expression2}.

Würden wir diesen Befehl in unserer if-Schleife benutzen, dann müsste er wie folgt geschrieben sein: if test $# -ne 2. Die Shell verfügt über viele integrierte Funktionen wie test. Man nennt sie, was nicht überrascht [ $# -ne 2 ]. Das Schlüsselwort test ist ein builtin genauso wie [, mit derselben Bedeutung.

Die Nutzung von Tests ist von so zentraler Bedeutung für die Kontrolle des Programmablaufes durch Shell scripting, dass Sie die Verfügbaren Tests kennen sollten. Im Folgenden finden Sie eine vollständige Liste von verfügbaren Tests, wie sie in der offiziellen man-page Dokumentation aufgeführt ist.

Wenn EXP ein Ausdruck ist:

( EXP )         EXP ist wahr
! EXP           EXP ist falsch
EXP1 -a EXP2    Beide Ausdrücke sind wahr (logisches und)
EXP1 -o EXP2    Entweder EXP1 ist wahr oder EXP2 ist wahr (logisches oder)

Wenn STR eine Zeichenkette ist:

-n STR         Die Länge der Zeichenkette ist nicht null
STR            Äquivalent zu -n STR
-z STR         Die Länge der Zeichenkette ist null
STR1 = STR2    Die Zeichenketten sind identisch (gleich)
STR1 != STR2   Die Zeichenketten sind nicht identisch (nicht gleich)

WWenn INT eine Ganze Zahl ist:

INT1 -eq INT2  INT1 ist gleich INT2
INT1 -ge INT2  INT1 ist größer oder gleich INT2
INT1 -gt INT2  INT1 ist größer als INT2
INT1 -le INT2  INT1 ist kleiner oder gleich INT2
INT1 -lt INT2  INT1 ist kleiner als INT2
INT1 -ne INT2  INT1 ist nicht gleich INT2

Wenn F eine Datei ist:

F1 -ef F2      F1 und F2 haben gleiche Inodes und Gerätenummern
F1 -nt F2      F1 ist neuer (Änderungsdatum) als F2
F1 -ot F2      F1 ist älter als F2
-b F           F existiert und ist blockspezifisch
-c F           F existiert und ist zeichenspezifisch
-d F           F existiert und ist ein Verzeichnis
-e F           F existiert
-f F           F existiert und ist eine reguläre Datei
-g F           F existiert und ist eine set-group-ID
-G F           F existiert und Besitzer ist die effektive group-ID
-h F           F existiert und ist ein symbolischer Link (wie -L)
-k F           F existiert und sein sticky bit ist gesetzt
-L F           F existiert und ist ein symbolischer Link (wie -h)
-O F           F existiert und Besitzer ist die effektive Benutzer ID
-p F           F existiert und ist eine benannte pipe
-r F           F existiert und Leseerlaubnis ist erteilt
-s F           F existiert und ist größer als Null
-S F           F existiert und ist ein socket
-t FD          Der file descriptor FD ist in einem Terminal geöffnet
-u F           F existiert und das set-user-ID bit ist gesetzt
-w F           F existiert und hat Schreiberlaubnis
-x F           F existiert und hat Erlaubnis zum Ausführen (oder zum Suchen)

Diese Liste gibt Ihnen einen Überblick über die Flexibilität, die Ihnen zum Ausführen eines Tests zur Verfügung steht.

Anmerkung! Der -e Test für das Vorhandensein einer Datei kann auch mit -a erfolgen. Ich verwende dies aber nicht, weil es leicht mit dem -a Test (dem logischen und) verwechselt werden kann. In anderen Scripts kann man das aber finden.

Das if-then Statement kann auch das else Schlüsselwort enthalten. Das geht dann so:

if(Bedingung)
  then     Befehle, die bei Zutreffen der Bedingung auszuführen sind
  else     Befehle, die bei Nichtzutreffend der Bedingung auszuführen sind
fi

Im nächsten Beispiel verwende ich den Befehl read mit dem ein Benutzer während ein Script läuft einfach Eingaben in Variable durchführen kann.

pic

Nach dem ersten Befehl wartet das Script auf eine Eingabe des Benutzers über die Tastatur mit Drücken der Enter Taste als Abschluss. Die Eingabe des Benutzers wird in der Variablen ANSWER gespeichert. Diesmal führt das Script unterschiedliche Dinge aus, je nachdem, was der Benutzer eingibt.

Aber was passiert, wenn der Benutzer etwas anderes als Y oder N eingibt? Um damit klarzukommen führen wir ein weiteres Schlüsselwort ein — elif.

pic

In diesem Script werden die Eingaben, die akzeptierbar sind angenommen und entsprechend weiter verfahren. Alle anderen Antworten (Eingaben) werden nach dem else Schlüsselwort weiter behandelt. Das würde eigentlich das Problem lösen, aber wenn die Enter Taste ohne vorherige Eingabe gedrückt wird, dann wurde der Variablen ANSWER nichts zugewiesen, was standardmäßig dem Nullwert entspricht. Das Script sieht dann die Tests als [ = y ] und [ = n ] an, was die Fehlermeldung unary operator expected generiert. Um dies zu vermeiden kann man die Variable in Doppelanführungszeichen setzen, was bewirkt, dass der Test [''=y] oder [''=n] vorfindet. Dies sind zulässige Ausdrücke, mit denen die Shell umgehen kann. Das '''' im Test ist ein leerer String (ein String ohne Zeichen) und das ist nicht dasselbe wie Null.

pic

Sie können so viele elif Tests einführen, wie Sie wollen und das if Statement kann so oft verschachtelt werden, wie Sie den Überblick behalten.


If [Bedingung]
then
   if [Bedingung]
   then
      if [Bedingung]
      …
      …
      …
      fi
   fi
fi

Natürlich kann jedes if Statement seine eigenen elif und else haben. Hier ist ein längeres Codebeispiel, wieder mit Zeilennummern.

pic

In Zeile 1 steht unsere Standardüberschrift für Scripte. Zeile 3 ist ein Kommentar und wird ignoriert.

In den Zeilen 4 und 5 wird die Datumsfunktion mit Parametern %m und %d verwendet, um das gegenwärtige Datum und den Monat in unser Variablen einzulesen.

In Zeile 6 beginnt die erste von zwei if-then Schleifen und es wird geprüft, ob der Monat kleiner oder gleich 3 (März) ist.

In Zeile 9 wird geprüft, ob der Monat größer 3 und kleiner oder gleich 6 ist (April, Mai oder Juni).

In Zeile 19 haben wir herausgefunden, dass wir im Dezember sind, also können wir die zweite if-then Schleife starten, um den Tag zu ermitteln.

Die Zeilen 23, 26 und 29 veranlassen weitere Tests.

Zeile 32 2 enthält das standardmäßige else Statement. Wenn wir bis dahin gekommen sind ist der Tag nicht im Bereich 1 – 31, es ist also etwas schiefgelaufen und wir verlassen das Script.

Zeile 36 führt uns zurück in die erste if-then Schleife zum else Statement. Wenn wir dahin kommen, war der Monat nicht im Bereich 1 – 12, also war wieder etwas falsch und wir verlassen das Script.

Zeile 39 beendet die erste if-then Schleife.

Das obige Script demonstriert den Gebrauch von Verschachtelungen des if-then Statements und von mehrfachen elifs. Es ist aber nicht der der einzige oder der effektivste Weg des Programmierens.

Wir hätten auch das case Statement verwenden können, ebenfalls ein bedingtes Statement. Hier ist die Syntax für das case Statement:


Case {Muster} in
   Wert1)
      Befehle
      ;;
   Wert2)
      Befehle
      ;;
   …
   …
   …
   *)
      Befehle
      ;;
esac

In dieser Struktur ist Muster irgendetwas wie der Inhalt einer Variablen, die Sie verwenden, um die Aktionen des Scripts zu kontrollieren. Wenn es Wert1 ist, werden die nachfolgenden durch ;; gekennzeichneten Befehle ausgeführt. Wert2 veranlasst die Ausführung einer anderen Serie von Befehlen und so weiter, bis alle Werte, die Sie überprüfen wollen abgearbeitet sind. Der Standard am Ende des Statements fängt jeden anderen Wert ab und wird zur Sicherheit vor unerwünschten oder unerwarteten Werten eingesetzt. Er kann auch dazu dienen das Script oder das Codesegment zu verlassen. Wenn Sie mehrere Werte testen wollen, trennen Sie diese durch das Pipe Symbol |.

Im nächsten Beispiel habe ich ein case Statement mit dem verschachtelten if-then Statement des vorigen Beispiels gemischt und wieder Zeilennummern eingefügt.

pic

Die Werte, die in den Zeilen 11 bis 15 getestet werden sind Ganzzahlen. Die Datumsfunktion gibt einen String mit 2 Zeichen wie ''02'' aus. Die Tests in den Zeilen 8 bis 12 würden daher wegen der vorgestellten 0 fehlschlagen. Um das zu umgehen geben wir den Wert mittels echo über eine Pipe an den translate Befehl tr weiter und verwenden die Option -d mit dem Argument "0,". Dies löscht alle Nullen in der Zeichenkette bis auf den String ''10'', der eine Ganzzahl ist. Der Ausdruck wird in back ticks ausgewertet und der neuen Variablen RAWMONTH zugewiesen.

Wir hätten im case Statement auch den 2-Zeichen-String, den Die Datumsfunktion zurückgegeben hat, verwenden können, aber indem wir Ganzzahlen verwenden, können wir sehr schön demonstrieren, dass man sich des Datentyps, den wir in Tests verwenden, unbedingt bewusst sein muss.

Hier ist jeder Test im case Statement nur eine Zeile lang, damit es kompakt bleibt. Wenn ein Test aus mehreren Befehlen besteht, sollten diese durch ein Semikolon oder durch das Zeichen für eine neue Zeile getrennt sein (neuer Befehl – neue Zeile). Ich denke, dass Sie mit mir übereinstimmen, dass das case Statement besser lesbar ist als die vielen elifs im if Statement.

Die if-then und case Strukturen sind Beispiele für konditionales Programmieren, bei dem der Fortschritt und die Richtung im Script durch die Ergebnisse gewisser Tests bestimmt ist. Die Shell besitzt zwei konditionale Operatoren, && und ||, die logisches und und logisches oder bedeuten. Beide funktionieren im unären (ein Argument) oder im binären (zwei Argumente) Modus.

Unärer Modus:

[ "$A" -gt 4 ] && echo "yes"

Wenn der Ausdruck [ "$A" -gt 4 ] wahr ist, wird der echo Befehl ausgeführt, wenn nicht ignoriert das Script die Unterbrechung und wird weiter ausgeführt.

Der || Operator führt zur entgegengesetzten Auswirkung, bei der die Auswertung des Ausdrucks falsch ergeben muss, damit der Befehl ausgeführt wird.

Im binären Modus werden 2 Argumente getestet:

if [ "$A" -lt 4 ] && [ "$B" -gt 9 ] echo "yes" Der echo Befehl wird nur ausgeführt, wenn alle beiden Argumente wahr sind.

if [ "$A" -lt 4 ] || [ "$B" -gt 9 ] echo "yes" Der echo Befehl wird ausgeführt, wenn eine der beiden Bedingungen oder alle beide wahr sind.

Dies ist ähnlich, aber nicht dasselbe wie bei den Testoperatoren -a und -o. Wenn wir die Testoperatoren verwenden, werden beide Ausdrücke ausgewertet und dann wird der Test durchgeführt. Der && Shell Operator wertet den ersten Ausdruck aus und wenn dieser falsch ist gibt es keinen Grund, sich die zweite Bedingung anzusehen und die 'und' Bedingung kann nicht erfüllt werden.

Gleicherweise: Wenn der erste Ausdruck in einem 'or' Test mit dem || Operator sich zu wahr ergibt, dann ist die Bedingung bereits erfüllt und der zweite Ausdruck braucht nicht untersucht zu werden. Aus diesem Grund werden sie auch Kurzschlussoperatoren genannt short circuit operators.

Der Anwendungsbereich von Variablen

Jetzt wo wir begonnen haben, unsere eigenen Variablen zu benutzen, ist es wichtig bevor wir weitermachen, den Anwendungsbereich von Variablen zu verstehen. Variablen werden angewendet, wenn der ihnen zugewiesene Wert wahr ist. Variable können lokal oder global sein. Sie können Variable erzeugen während Sie sich z. B. auf der Kommandozeile befinden weil bei Ihnen eine Shell aktiv ist.

pic

Der Anwendungsbereich dieser Variablen ist der gegenwärtig laufende Shell Prozess. Wenn Sie die Shell verlassen hört die Variable auf zu existieren und wenn Sie eine neue Shell starten, steht die Variable nicht mehr zur Verfügung, da sie für den Shell Prozess durch den sie erzeugt wurde eine lokale Variable war. Wenn Sie ein Script laufen lassen, wird ein neuer Shell Prozess gestartet und alle Variablen, die Sie erzeugen bleiben lokal auf dieses Script bezogen und sind anderswo nicht verfügbar.

Umgebungsvariable sind global und stehen allen Prozessen zur Verfügung Wenn Sie ihre Variablen anderen Prozessen zugänglich machen wollen, müssen sie zur Umgebung exportiert werden. Alle neuen Prozesse erben die Umgebung ihrer Elternprozesse. Wenn eine exportierte Variable an einen Kindprozess übergeben wird enthält sie den Wert, der ihr im Elternprozess zugewiesen wurde. Der Kindprozess kann den Wert verändern, aber der Wert, den der Elternprozess sieht, bleibt unverändert.

pic

Jane hat den Wert der Variablen AGE auf 22 (ihr Alter) gesetzt und diese exportiert. Durch das Ausführen des su Befehls und den Wechsel zum Benutzer john wurde ein neuer Shell Prozess gestartet, dem die Variable und ihr von Jane gesetzter Wert zugänglich ist. Diesen Wert hat john anschließend auf 19 (sein Alter) geändert. Janes Wert bleibt unverändert 22.

Um eine Variable zu entfernen verwenden wir den Befehl unset.

pic

Ein weiterer Befehl, der im Zusammenhang mit Variablen benutzt wird, ist readonly.Es bewirkt die Umwandlung einer Variablen in eine Konstante – das ist eine Variable, deren Wert – einmal gesetzt – nicht geändert werden kann. Beispielsweise readonly KB=1024. Der zugewiesene Wert kann während der Lebensdauer des Prozesses nicht geändert werden und readonly Variable können nicht mit unset entfernt werden.

Der Befehl env wird verwendet, um alle gegenwärtig gesetzten Umgebungsvariable anzuzeigen und um die Umgebung zu kontrollieren, die an Befehle übergeben wird. Der env Befehl alleine (ohne Zusätze) bewirkt die Ausgabe einer Liste aller gegenwärtig gesetzten Umgebungsvariablen. Wird der env Befehl zusammen mit einem oder mehreren Zusätzen vor einem Befehl verwendet, wird die Umgebung, die dem Befehl übergeben wird geändert. Die gegenwärtige Arbeitsumgebung bleibt aber unverändert. Mit der -i Option wird die gegenwärtige Umgebung ignoriert und nur die Variablen, die dem Befehl übergeben werden, werden verwendet.

pic

Hier wird die Umgebungsvariable HOME, die normalerweise den vollständigen Pfad zum Heimatverzeichnis des Benutzers enthält, zeitweise nach /tmp geändert. Alle anderen Umgebungsvariablen bleiben unberührt. Die neue Umgebung wird dann an den Befehl env übergeben, der in einem neuen Prozess startet und alle seine bekannten Umgebungsvariablen auflistet. Davon gibt es nur eine, da ja alle anderen unberührt geblieben waren.

Der Befehl env wird sofort in der aktuellen Shell ausgeführt und die Ausgabe sucht nach Zeilen, die mit dem Muster "HOME" beginnen. Das veränderte Umfeld gibt es nur für den Prozess den es übergeben wurde.