Kapitel 4. Ausführen von Befehlen

Diese Arbeit wurde mithilfe von KI übersetzt. Wir freuen uns über dein Feedback und deine Kommentare: translation-feedback@oreilly.com

Der Hauptzweck der Bash (oder jeder anderen Shell) besteht darin, dass du mit dem Betriebssystem deines Computers interagieren kannst, damit du das tun kannst, was du tun musst. In der Regel geht es dabei um das Starten von Programmen. Die Shell nimmt die Befehle entgegen, die du eingibst, bestimmt anhand dieser Eingaben, welche Programme ausgeführt werden müssen, und startet sie für dich.

Werfen wir einen Blick auf den grundlegenden Mechanismus zum Starten von Aufträgen und erkunden einige der Funktionen, die die Bash bietet, um Programme im Vorder- oder Hintergrund, sequentiell oder parallel zu starten, um anzuzeigen, ob sie erfolgreich waren und vieles mehr.

4.1 Jede ausführbare Datei ausführen

Problem

Du musst einen Befehl auf einem Linux- oder Unix-System ausführen.

Lösung

Verwende die Bash und gib den Namen des Befehls in die Eingabeaufforderung ein:

$ someprog

Diskussion

Das scheint ziemlich einfach zu sein, und in gewisser Weise ist es das auch, aber hinter den Kulissen geht eine Menge vor sich, die du nie siehst. Was du über die Bash wissen musst, ist, dass ihre Grundfunktion darin besteht, Programme zu laden und auszuführen. Alles andere ist nur Augenwischerei, um Programme ausführen zu können. Sicher, es gibt Shell-Variablen und Steueranweisungen für Schleifen und if/then/else und Verzweigungen, und es gibt Möglichkeiten, die Ein- und Ausgabe zu steuern, aber das sind nur die Sahnehäubchen auf der Torte der Programmausführung.

Woher kommt also das Programm zum Laufen?

DieBash verwendet eine Shell-Variable namens $PATH, um deine ausführbare Datei zu finden. Die Variable $PATH ist eine Liste von Verzeichnissen. Die Verzeichnisse werden durch Doppelpunkte (:) getrennt. Die Bash sucht in jedem dieser Verzeichnisse nach einer Datei mit dem von dir angegebenen Namen. Die Reihenfolge der Verzeichnisse ist wichtig - die Bash schaut sich die Reihenfolge an, in der die Verzeichnisse in der Variable aufgelistet sind, und nimmt die erste gefundene ausführbare Datei:

$ echo $PATH
/bin:/usr/bin:/usr/local/bin:.
$

In der hier gezeigten Variable $PATH sind vier Verzeichnisse enthalten. Das letzte Verzeichnis in der Liste ist ein einzelner Punkt (das so genannte Dot-Verzeichnis oder einfach nur Dot), der das aktuelle Verzeichnis auf einem Linux- oder Unix-Dateisystem darstellt - wo auch immer du dich befindest, das ist das Verzeichnis, auf das Dot verweist. Wenn du zum Beispiel eine Datei von irgendwoher nach dot kopierst (d. h., cp /other/place/file . ) kopierst, kopierst du die Datei in das aktuelle Verzeichnis. Durch die Angabe des Verzeichnisses dot in deinem Pfad wird die Bash angewiesen, nicht nur in den anderen Verzeichnissen nach Befehlen zu suchen, sondern auch im aktuellen Verzeichnis (.).

Viele Menschen sind der Meinung, dass mit dot in der $PATH ein zu großes Sicherheitsrisiko darstellt - jemand könnte dich austricksen und dich dazu bringen, seine eigene bösartige Version eines Befehls (z. B. ls) anstelle des erwarteten Befehls auszuführen. Wenn dot an erster Stelle aufgeführt wäre, würde die Version von ls den normalen ls-Befehl ersetzen und du könntest diesen Befehl unwissentlich ausführen. Du glaubst uns nicht? Probier das mal aus:

$ bash
$ cd
$ touch ls
$ chmod 755 ls
$ PATH=".:$PATH"
$ ls
$

Plötzlich scheint ls in deinem Heimatverzeichnis nicht zu funktionieren. Du bekommst keine Ausgabe. Wenn du mit cd zu einem anderen Ort wechselst (z. B. cd /tmp), funktioniert ls, aber nicht in deinem Heimatverzeichnis. Warum? Weil es in diesem Verzeichnis eine leere Datei namens ls gibt, die anstelle des normalen ls-Befehls unter /bin/ls ausgeführt wird (und nichts tut - sie ist leer). Da wir zu Beginn dieses Beispiels eine neue Bash-Kopie gestartet haben, kannst du diesen Schlamassel verlassen, indem du diese Subshell beendest - aber vielleicht möchtest du zuerst den falschen ls-Befehl entfernen:

$ cd
$ rm ls
$ exit
$

Kannst du dir vorstellen, dass es gefährlich sein kann, in ein fremdes Verzeichnis zu gehen, wenn dein $PATH so eingestellt ist, dass es zuerst das Dot-Verzeichnis und dann alle anderen Verzeichnisse durchsucht?

Wenn du den Punkt als letztes Verzeichnis in deine $PATH Variable einträgst, kannst du zumindest nicht so leicht überlistet werden. Natürlich ist es sogar noch sicherer, wenn du ihn ganz weglässt. Dann kannst du immer noch Befehle in deinem lokalen Verzeichnis ausführen, indem du einen führenden Punkt und einen Schrägstrich eintippst, wie in:

./myscript

Du hast die Wahl.

Warnung

Erlaube niemals dot oder beschreibbare Verzeichnisse in root's $PATH. Mehr zu diesem Thema findest du in Rezept 14.9 und Rezept 14.10.

Vergiss nicht, Ausführungsrechte für die Datei zu setzen, bevor du dein Skript aufrufst:

chmod +x myscript

Du musst die Berechtigungen nur einmal festlegen. Danach kannst du das Skript als Befehl aufrufen.

Bei einigen Bash-Benutzern ist es üblich, ein persönliches bin-Verzeichnis anzulegen, analog zu den Systemverzeichnissen /bin und /usr/bin, in denen ausführbare Dateien gespeichert werden. In deinem persönlichen bin-Verzeichnis (wenn du es in deinem Home-Verzeichnis anlegst, lautet der Pfad ~/bin) kannst du Kopien deiner Lieblingsshellskripte und andere angepasste oder private Befehle ablegen. Dann füge dieses Verzeichnis zu deinem $PATH hinzu, sogar ganz vorne (PATH=~/bin:$PATH). Auf diese Weise kannst du immer noch deine eigenen angepassten Favoriten haben, ohne das Sicherheitsrisiko, Befehle von Fremden auszuführen.

4.2 Mehrere Befehle nacheinander ausführen

Problem

Du musst mehrere Befehle ausführen, aber manche brauchen eine Weile und du willst nicht warten, bis jeder einzelne beendet ist, bevor du den nächsten Befehl gibst.

Lösung

Es gibt drei Lösungen für dieses Problem, wobei die erste ziemlich trivial ist: Tippe einfach weiter. Ein Linux- oder Unix-System ist so fortschrittlich, dass es dich weiter tippen lässt, während es deine vorherigen Befehle abarbeitet.

Eine andere, recht einfache Lösung ist, diese Befehle in eine Datei zu schreiben und dann die Bash anzuweisen, die Befehle in der Datei auszuführen - also ein einfaches Shell-Skript. Nehmen wir zum Beispiel an, dass wir drei Befehle ausführen wollen, lang, mittel und kurz, deren Ausführungszeit jeweils in ihrem Namen angegeben ist. Wir müssen sie in dieser Reihenfolge ausführen, wollen aber nicht warten, bis das lange Skript fertig ist, bevor wir die anderen Befehle eingeben. Wir könnten ein Shell-Skript (auch bekannt als Batch-Datei) verwenden. Hier ist eine primitive Methode, das zu tun:

$ cat > simple.script
long
medium
short
^D                      # Ctrl-D, not visible
$ bash ./simple.script

Die dritte und wohl beste Lösung ist, die einzelnen Befehle nacheinander auszuführen. Wenn du jedes Programm unabhängig davon ausführen willst, ob die vorangegangenen fehlgeschlagen sind, trennst du mit einem Semikolon:

long ; medium ; short

Wenn du das nächste Programm nur ausführen willst, wenn das vorhergehende Programm funktioniert hat und alle Programme korrekt gesetzte Exit-Codes haben, trennst du sie mit doppelten Ampersands:

long && medium && short

Diskussion

Das cat-Beispiel war nur eine sehr primitive Art, Text in eine Datei einzugeben: Wir haben die Ausgabe des Befehls in die Datei namens simple.script umgeleitet (mehr über das Umleiten von Ausgaben erfährst du in Kapitel 2). Besser ist es, einen richtigen Editor zu benutzen, aber solche Dinge sind in Beispielen wie diesem schwieriger zu zeigen. Von nun an werden wir, wenn wir ein Skript zeigen wollen, den Text entweder nur als körperlosen Text zeigen, der nicht in einer Befehlszeile steht, oder wir beginnen das Beispiel mit einem Befehl wie cat filename ein, um den Inhalt der Datei auf den Bildschirm zu bringen (anstatt die Ausgabe unserer Eingaben in die Datei umzuleiten) und ihn so im Beispiel anzuzeigen.

Mit dieser einfachen Lösung soll vor allem gezeigt werden, dass mehr als ein Befehl in die Bash-Befehlszeile eingegeben werden kann. Im ersten Fall wird der zweite Befehl erst ausgeführt, wenn der erste Befehl beendet wird, der dritte wird erst ausgeführt, wenn der zweite beendet wird, und so weiter, für so viele Befehle, wie du in der Zeile hast. Im zweiten Fall wird der zweite Befehl erst ausgeführt, wenn der erste Befehl erfolgreich war, der dritte wird erst ausgeführt, wenn der zweite erfolgreich war, und so weiter, für so viele Befehle, wie du in der Zeile hast.

Siehe auch

4.3 Mehrere Befehle auf einmal ausführen

Problem

Du musst drei Befehle ausführen, aber sie sind unabhängig voneinander und müssen nicht darauf warten, dass die vorherigen Befehle abgeschlossen sind.

Lösung

Du kannst einen Befehl im Hintergrund ausführen, indem du ein kaufmännisches Und (&) an das Ende der Befehlszeile setzt. Du könntest also alle drei Befehle kurz hintereinander abfeuern, wie folgt

$ long &
[1] 4592
$ medium &
[2] 4593
$ short
$

Oder noch besser: Du kannst alles in einer Befehlszeile erledigen:

$ long & medium & short
[1] 4592
[2] 4593
$

Diskussion

Wenn wir einen Befehl "im Hintergrund" ausführen (einen solchen Ort gibt es in Linux eigentlich nicht), bedeutet das eigentlich nur, dass wir die Tastatureingabe vom Befehl trennen und die Shell nicht wartet, bis der Befehl abgeschlossen ist, bevor sie eine weitere Eingabeaufforderung ausgibt und weitere Befehlseingaben akzeptiert. Die Ausgabe des Befehls erfolgt weiterhin auf dem Bildschirm (es sei denn, wir ändern dieses Verhalten explizit), so dass in diesem Beispiel alle drei Befehle Ausgaben auf dem Bildschirm erzeugen.

Die ungeraden Bits der numerischen Ausgabe sind die Auftragsnummer in eckigen Klammern, gefolgt von der Prozess-ID des Befehls, den wir gerade im Hintergrund gestartet haben. In unserem Beispiel ist Auftrag 1 (Prozess 4592) der lange Befehl und Auftrag 2 (Prozess 4593) der mittlere.

Wir haben short nicht in den Hintergrund gestellt, da wir kein kaufmännisches Und am Ende der Zeile gesetzt haben, also wartet die Bash, bis sie fertig ist, bevor sie uns die Eingabeaufforderung (die $) gibt.

Die Auftragsnummer oder Prozess-ID kann verwendet werden, um eine begrenzte Kontrolle über einen Auftrag zu erhalten. Wir könnten z. B. den langen Auftrag mit kill %1 beenden (da seine Auftragsnummer 1 war) oder wir könnten die Prozessnummer (d. h. kill 4592) mit demselben tödlichen Ergebnis angeben.

Du kannst die Auftragsnummer auch verwenden, um dich wieder mit einem Hintergrundauftrag zu verbinden. Wir könnten z.B. den langen Auftrag mit fg %1 wieder mit dem Vordergrund von verbinden. Wenn du nur einen Auftrag im Hintergrund laufen hast, brauchst du die Auftragsnummer gar nicht, sondern kannst einfach fg verwenden.

Tipp

Wenn du einen Befehl ausführst und feststellst, dass er länger dauert als gedacht, kannst du ihn mit Strg-Z unterbrechen, um zur Eingabeaufforderung zurückzukehren. Dann kannst du bg eingeben, um die Unterbrechung aufzuheben und den Auftrag im Hintergrund weiterlaufen zu lassen. Damit fügst du im Grunde genommen nachträglich ein & hinzu.

4.4 Erkennen, ob ein Befehl erfolgreich war oder nicht

Problem

Du musst wissen, ob der Befehl, den du ausgeführt hast, erfolgreich war.

Lösung

Die Shell-Variable $? wird auf einen Wert ungleich Null gesetzt, wenn der Befehl fehlschlägt - vorausgesetzt, der Programmierer, der den Befehl oder das Shell-Skript geschrieben hat, hat sich an die etablierte Konvention gehalten:

$ somecommand
# it works...
$ echo $?
0
$ badcommand
# it fails...
$ echo $?
1
$

Diskussion

Der Exit-Status eines Befehls wird in der Shell-Variablen festgehalten, auf die mit $? verwiesen wird. Sein Wert kann von 0 bis 255 reichen. Wenn du ein Shell-Skript schreibst, ist es eine gute Idee, dein Skript mit Null zu beenden, wenn alles in Ordnung ist, und mit einem Wert ungleich Null, wenn du auf einen Fehlerzustand stößt. Wir empfehlen, nur 0 bis 127 zu verwenden, da die Shell 128+N verwendet, um den Abbruch durch das Signal N zu kennzeichnen. Wenn du eine Zahl größer als 255 oder kleiner als 0 verwendest, werden die Zahlen umbrochen. Mit der Anweisung exit gibst du einen Exit-Status zurück (z. B. exit 1 oder exit 0). Beachte aber, dass du nur einen Versuch hast, den Exit-Status eines Befehls zu lesen:

$ badcommand
# it fails...
$ echo $?
1
$ echo $?
0
$

Warum liefert uns das zweite echo 0 als Ergebnis? Es meldet den Status des unmittelbar vorangegangenen echo-Befehls. Das erste Mal, als wir echo $? eingegeben haben, wurde 1 zurückgegeben, was der Rückgabewert von badcommand war. Aber der Echo-Befehl selbst war erfolgreich und deshalb ist der neue, letzte Status ein Erfolg (d.h. ein 0 Wert). Da du nur einmal die Möglichkeit hast, den Exit-Status zu überprüfen, weisen viele Shell-Skripte den Status sofort einer anderen Shell-Variablen zu, wie in:

$ badcommand
# it fails...
$ STAT=$?
$ echo $STAT
1
$ echo $STAT
1
$

Wir können den Wert in der Variablen $STAT speichern und ihn später überprüfen.

Auch wenn wir das in den Beispielen für die Befehlszeile zeigen, werden Variablen wie $? vor allem beim Schreiben von Skripten verwendet. Du kannst normalerweise sehen, ob ein Befehl funktioniert hat oder nicht, wenn du ihn auf deinem Bildschirm ausführst. Aber in einem Skript können die Befehle unbeaufsichtigt ausgeführt werden.

Eine der großartigen Eigenschaften der Bash ist, dass die Skriptsprache identisch mit den Befehlen ist, die du an einer Eingabeaufforderung in einem Terminalfenster eingibst. Das macht es viel einfacher, die Syntax und Logik zu überprüfen, während du deine Skripte schreibst.

Der Exit-Status wird häufig in Skripten und oft auch in if Anweisungen verwendet, um je nach Erfolg oder Misserfolg eines Befehls unterschiedliche Aktionen auszuführen. Hier ist zunächst ein einfaches Beispiel, aber wir werden dieses Thema in zukünftigen Rezepten wieder aufgreifen:

somecommand
...
if (( $? )) ; then echo failed ; else echo OK; fi

(( )) wertet einen arithmetischen Ausdruck aus; siehe Rezepte 6.1 und 6.2.

Wir raten auch davon ab, negative Zahlen zu verwenden. Die Shell akzeptiert sie zwar ohne Fehler, aber sie tut nicht das, was du erwartest:

$ bash -c 'exit -2' ; echo $?
254

$ bash -c 'exit -200' ; echo $?
56

4.5 Einen Befehl nur ausführen, wenn ein anderer Befehl erfolgreich war

Problem

Du musst einige Befehle ausführen, aber du willst bestimmte Befehle nur ausführen, wenn bestimmte andere erfolgreich sind. Du möchtest zum Beispiel mit dem cd-Befehl in ein temporäres Verzeichnis wechseln und alle Dateien entfernen. Du möchtest aber keine Dateien entfernen, wenn cd fehlschlägt (z. B. wenn die Rechte dich nicht in das Verzeichnis lassen, oder wenn du den Verzeichnisnamen falsch schreibst).

Lösung

Du kannst den Exit-Status ($?) des cd-Befehls in Kombination mit einer if Anweisung verwenden, um das rm nur dann auszuführen, wenn das cd erfolgreich war:

cd mytmp
if (( $? == 0 )); then rm * ; fi
Tipp

Eine bessere Möglichkeit, dies zu schreiben, ist die folgende, aber wir denken, es ist klarer, es so zu zeigen und zu erklären, wie wir es getan haben:

if cd mytmp; then rm * ; fi

Diskussion

Natürlich müsstest du das nicht tun, wenn du die Befehle von Hand eintippen würdest. Du würdest alle Fehlermeldungen des cd-Befehls sehen und deshalb den rm-Befehl nicht eingeben. Bei Skripten ist das anders, und dieser Test ist in einem Skript wie unserem Beispiel sehr sinnvoll, um sicherzustellen, dass du nicht versehentlich alle Dateien in dem Verzeichnis löschst, in dem du es ausführst.

Angenommen, du führst das Skript aus dem falschen Verzeichnis aus, in dem es kein Unterverzeichnis namens mytmp gibt. Das cd würde fehlschlagen, sodass das aktuelle Verzeichnis unverändert bliebe. Ohne die Prüfung von if (dass das cd fehlgeschlagen ist) würde das Skript einfach mit der nächsten Anweisung fortfahren. Die Ausführung von rm * würde alle Dateien in deinem aktuellen Verzeichnis löschen. Autsch. Die if ist es wert.

Woher bekommt $? seinen Wert? Es ist der Exit-Code des Befehls (siehe Rezept 4.4). Für C-Programmierer ist dies der Wert des Arguments, das der Funktion exit() übergeben wird; exit(4); würde z. B. 4 zurückgeben. Für die Shell gilt ein Exit-Code von Null als Erfolg und ein Wert ungleich Null als Fehler.

Wenn du Bash-Skripte schreibst, solltest du darauf achten, dass du die Rückgabewerte explizit festlegst, damit $? von deinem Skript richtig gesetzt wird. Wenn du das nicht tust, wird der Wert des zuletzt ausgeführten Befehls gesetzt, was du vielleicht nicht als Ergebnis haben willst.

4.6 Weniger if-Anweisungen verwenden

Problem

Als gewissenhafter Programmierer hast du dir zu Herzen genommen, was wir im vorherigen Rezept beschrieben haben. Du hast das Konzept auf dein neuestes Shell-Skript angewandt, aber jetzt stellst du fest, dass das Shell-Skript unleserlich ist, mit all den if Anweisungen, die den Rückgabewert jedes Befehls überprüfen. Gibt es nicht eine Alternative?

Lösung

Verwende den Operator double-ampersand in der Bash, um bedingte Ausführung zu ermöglichen:

cd mytmp && rm *

Diskussion

Das Trennen zweier Befehle durch die doppelten Ampersands weist die Bash an, den ersten Befehl auszuführen und den zweiten Befehl nur dann auszuführen, wenn der erste Befehl erfolgreich war (d.h. sein Exit-Status 0 ist). Dies ist vergleichbar mit einer if Anweisung, die den Exit-Status des ersten Befehls überprüft, um die Ausführung des zweiten Befehls zu schützen:

cd mytmp
if (( $? == 0 )); then rm * ; fi

Die Syntax des Doppel-Ampersands soll an den logischen UND-Operator in der Sprache C erinnern. Wenn du dich mit Logik (und C) auskennst, wirst du dich daran erinnern, dass bei der Auswertung des logischen Ausdrucks A AND B der gesamte Ausdruck nur wahr sein kann, wenn sowohl der (Unter-)Ausdruck A als auch der (Unter-)Ausdruck B wahr sind. Wenn einer der beiden Ausdrücke falsch ist, ist der gesamte Ausdruck falsch. Die Sprache C macht sich diese Tatsache zunutze. Wenn du einen Ausdruck wie if (A && B) { ... } codierst, wird zuerst der Ausdruck A ausgewertet. Wenn er falsch ist, wird B gar nicht ausgewertet, da das Gesamtergebnis (falsch) bereits feststeht (weil A falsch ist).

Was hat das nun mit der Bash zu tun ? Wenn der Exit-Status des ersten Befehls (der links von &&) ungleich Null ist (d.h. fehlgeschlagen ist), wird der zweite Ausdruck nicht ausgewertet - der andere Befehl wird gar nicht erst ausgeführt.

Wenn du bei der Fehlerprüfung gründlich sein willst, aber nicht überall if Anweisungen haben willst, kannst du die Bash dazu bringen, sich jedes Mal zu beenden, wenn sie einen Fehler (d.h. einen Exit-Status ungleich Null) bei jedem Befehl in deinem Skript feststellt (außer in while Schleifen und if Anweisungen, wo sie den Exit-Status bereits erfasst und verwendet), indem du das -e Flag setzt:

set -e
cd mytmp
rm *

Wenn du das Flag -e setzt, wird die Shell beendet, wenn ein Befehl fehlschlägt. Wenn das cd in diesem Beispiel fehlschlägt, wird das Skript beendet und versucht gar nicht erst, den Befehl rm * auszuführen. Wir empfehlen jedoch nicht, dies auf einer interaktiven Shell zu tun, denn wenn die Shell beendet wird, verschwindet auch dein Shell-Fenster.

Siehe auch

4.7 Lange Aufträge unbeaufsichtigt ausführen

Problem

Du hast einen Job im Hintergrund ausgeführt, dann die Shell verlassen und einen Kaffee getrunken. Als du zurückkamst, um nachzusehen, lief der Job nicht mehr und war nicht abgeschlossen. Tatsächlich war dein Auftrag noch nicht sehr weit fortgeschritten. Er scheint beendet worden zu sein, sobald du die Shell verlassen hast.

Lösung

Wenn du einen Auftrag im Hintergrund laufen lassen willst und davon ausgehst, dass du die Shell verlässt, bevor der Auftrag abgeschlossen ist, dann musst du den Auftrag nohupen:

$ nohup long &
nohup: appending output to `nohup.out'
$

Diskussion

Wenn du einen Auftrag in den Hintergrund stellst (über das &, wie in Rezept 4.3 beschrieben), ist er immer noch ein Kindprozess der Bash-Shell. Wenn du eine Instanz der Shell beendest, sendet die Bash ein Hangup-Signal (hup) an alle ihre Kindprozesse. Das ist der Grund, warum dein Auftrag nicht sehr lange lief. Sobald du die Bash beendet hast, hat sie deinen Hintergrundjob beendet. (Hey, du wolltest gehen; woher sollte sie das wissen?)

Der Befehl nohup richtet den Kindprozess einfach so ein, dass er Aufhängungssignale ignoriert. Du kannst den Job immer noch mit dem kill-Befehl beenden, denn kill sendet ein SIGTERM -Signal und kein SIGHUP -Signal. Aber mit nohup wird die Bash deinen Auftrag nicht versehentlich beenden, wenn du ihn beendest.

Die Meldung, die nohup über das Anhängen deiner Ausgabe ausgibt, ist nur der Versuch von nohup, dir zu helfen. Da du nach dem nohup-Befehl wahrscheinlich die Shell beendest, wird dein Ausgabeziel verschwinden, d.h. die Bash-Sitzung in deinem Terminal ist nicht mehr aktiv, so dass der Job nicht in der Lage ist, in STDOUT zu schreiben. Noch wichtiger ist, dass das Schreiben an ein nicht vorhandenes Ziel zu einem Fehler führen würde. Deshalb leitet nohup die Ausgabe für dich um, indem es sie an eine Datei namens nohup.out im aktuellen Verzeichnis anhängt (nicht überschreibt, sondern am Ende hinzufügt). Du kannst die Ausgabe in der Befehlszeile explizit an eine andere Stelle umleiten. nohup ist dann schlau genug, um dies zu erkennen und nohup.out nicht für deine Ausgabe zu verwenden.

Siehe auch

4.8 Anzeige von Fehlermeldungen bei Fehlern

Problem

Dein Shell-Skript muss bei Fehlern ausführlich sein. Du willst Fehlermeldungen sehen, wenn Befehle nicht funktionieren, aber if Anweisungen lenken oft vom visuellen Fluss der Anweisungen ab.

Lösung

Eine gängige Redewendung einiger Shell-Programmierer ist es, die || mit Befehlen zu verwenden, um Debug- oder Fehlermeldungen auszuspucken. Hier ist ein Beispiel:

cmd || printf "%b" "cmd failed. You're on your own\n"

Diskussion

Ähnlich wie && in Rezept 4.6 der Bash sagt, dass sie den zweiten Ausdruck nicht auswerten soll, wenn der erste falsch ist, sagt || der Shell, dass sie den zweiten Ausdruck nicht auswerten soll, wenn der erste wahr ist (d.h. Erfolg hat). Wie && erinnert auch die Syntax von || an die Logik und die Sprache C, in der das Ergebnis bereits feststeht (als wahr), wenn der erste Ausdruck in A OR B als wahr bewertet wird - der zweite Ausdruck muss also nicht bewertet werden. In der Bash geht es weiter, wenn der erste Ausdruck 0 zurückgibt (d.h. erfolgreich ist). Nur wenn der erste Ausdruck einen Wert ungleich Null zurückgibt (d.h. wenn der Exit-Wert des Befehls einen Fehler anzeigt), muss der zweite Teil ausgewertet und der andere Befehl ausgeführt werden.

Warnung: Lass dich davon nicht täuschen:

cmd || printf "%b" "FAILED.\n" ; exit 1

Der Exit wird in jedem Fall ausgeführt! Das ODER liegt nur zwischen den ersten beiden Befehlen. Wenn wir wollen, dass der Exit nur im Fehlerfall ausgeführt wird, müssen wir ihn mit printf gruppieren, damit beide als Einheit betrachtet werden. Die gewünschte Syntax würde lauten:

cmd || { printf "%b" "FAILED.\n" ; exit 1 ; }

Beachte, dass das Semikolon nach dem letzten Befehl und direkt vor } erforderlich ist und dass die schließende Klammer durch Leerzeichen vom umgebenden Text getrennt sein muss. Siehe Rezept 2.14 für eine Diskussion.

4.9 Befehle über eine Variable ausführen

Problem

Du möchtest in deinem Skript je nach Situation verschiedene Befehle ausführen. Wie kannst du variieren, welche Befehle ausgeführt werden?

Lösung

Es gibt viele Lösungen für dieses Problem - darum geht es beim Skripting. In den nächsten Kapiteln werden wir verschiedene Programmierkonstrukte besprechen, die zur Lösung dieses Problems verwendet werden können, z. B. if/then/else, case und andere Anweisungen. Aber hier ist ein etwas anderer Ansatz, der etwas über die Bash verrät. Wir können den Inhalt einer Variablen (mehr dazu in Kapitel 5) nicht nur für Parameter, sondern auch für den Befehl selbst verwenden:

FN=/tmp/x.x
PROG=echo
$PROG $FN
PROG=cat
$PROG $FN

Diskussion

Wir können den Programmnamen einer Variablen zuweisen (hier verwenden wir $PROG). Wenn wir dann an der Stelle, an der ein Befehlsname erwartet wird, auf diese Variable verweisen, verwendet die Bash den Wert dieser Variable ($PROG) als auszuführenden Befehl. Sie analysiert die Befehlszeile, ersetzt die Werte ihrer Variablen, nimmt das Ergebnis aller Ersetzungen und behandelt es als Befehlszeile, als ob es wortwörtlich so eingegeben worden wäre.

Warnung

Sei vorsichtig mit den Namen der Variablen, die du verwendest. Einige Programme, wie z. B. InfoZip, verwenden Umgebungsvariablen wie $ZIP und $UNZIP, um Einstellungen an das Programm selbst weiterzugeben. Wenn du also etwas wie ZIP=/usr/bin/zip machst, kannst du dir tagelang die Haare raufen und dich wundern, warum es in der Kommandozeile funktioniert, aber nicht in deinem Skript. Vertrau uns. Wir haben das auf die harte Tour gelernt. Außerdem: RTFM.

Siehe auch

4.10 Alle Skripte in einem Verzeichnis ausführen

Problem

Du willst eine Reihe von Skripten ausführen, aber die Liste ändert sich ständig; du fügst immer wieder neue Skripte hinzu, aber du willst nicht ständig eine Masterliste ändern.

Lösung

Lege die Skripte, die du ausführen willst, in ein Verzeichnis und lass die Bash alles ausführen, was sie findet. Anstatt eine Masterliste zu führen, kannst du auch einfach den Inhalt des Verzeichnisses als Masterliste verwenden. Hier ist ein Skript, das alles ausführt, was es in einem bestimmten Verzeichnis findet:

for SCRIPT in /path/to/scripts/dir/*
do
    if [ -f "$SCRIPT" -a -x "$SCRIPT" ]
    then
        $SCRIPT
    fi
done

Diskussion

Auf die Schleife for und die Anweisung if gehen wir in Kapitel 6 genauer ein, aber hier bekommst du einen Vorgeschmack. Die Variable $SCRIPT nimmt für jede Datei, die mit dem Platzhaltermuster * übereinstimmt, nacheinander Werte an, die auf alles im genannten Verzeichnis zutreffen (außer auf unsichtbare Punktdateien, deren Namen mit einem Punkt beginnen). Wenn es sich um eine Datei handelt (der -f Test) und die Ausführungsrechte gesetzt sind (der -x Test), versucht die Shell, das Skript auszuführen.

In diesem einfachen Beispiel haben wir keine Möglichkeit vorgesehen, Argumente für die Skripte anzugeben, wenn sie ausgeführt werden. Dieses einfache Skript mag für deine persönlichen Bedürfnisse gut funktionieren, aber es ist nicht sehr robust; manche würden es sogar für gefährlich halten. Wir hoffen aber, dass es dir einen Eindruck davon vermittelt, was dich erwartet: Skripting-Möglichkeiten im Stil einer Programmiersprache.

Siehe auch

  • Kapitel 6 für mehr über for Schleifen und if Anweisungen

Get bash Kochbuch, 2. Auflage now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.