Wir haben gesehen, wie man einfache Befehle schreibt, bei denen auf einen Befehl Argumente folgen. Jetzt sehen wir, wie man komplexere Befehle bildet, indem man einfache Befehle zusammensetzt.
Bevor wir anfangen, eine kurze Einführung in Eingabe und Ausgabe.
Prozesse haben „Standard-I/O-Kanäle“.
Der Befehl tr ist ein sehr reines Beispiel dafür.
Er liest nur Text aus seiner Eingabe, führt Zeichentransliterationen durch und schreibt den resultierenden Text in seine Ausgabe.
Wie man „stdio“-Kanäle manipuliert, sehen wir später im Lehrplan.
Das ist eines der „Killer-Features“ der Shell-Programmierung. Pipelines ermöglichen es dir, anspruchsvolle Transformationen auf einem Textstrom durchzuführen.
Um eine sortierte Liste von Benutzern zu erzeugen:
cat /etc/passwd | cut -d : -f 1 | sort
Das Pipe-Symbol (|) verbindet die Ausgabe eines Befehls mit der Eingabe eines anderen.
cut liest die Ausgabe von cat, und sort liest die Ausgabe von cut.
Standardmäßig läuft jeder Befehl einer Pipeline in einer eigenen Subshell. (Eine Subshell ist ein Kindprozess, der eine Kopie der aktuell laufenden Shell ist.)
Pipelines zu verwenden kostet Performance.
Wenn du lange Pipelines mit ähnlichen Befehlen hast, solltest du sie zu einem einzigen Befehl zusammenfassen.
Zum Beispiel lassen sich Pipelines, die mehrfach grep, cut, sed, awk und tr verwenden, in der Regel aus Effizienzgründen zu einem einzigen awk-Befehl zusammenfassen.
Der Exit-Status einer Pipeline ist der Exit-Status des letzten Befehls in der Pipeline. Das heißt: Wenn ein früherer Befehl fehlgeschlagen ist, der letzte Befehl aber erfolgreich war, ist der Exit-Status der ganzen Pipeline null.
Es gibt eine Shell-Einstellung, die das ändern kann;
wenn die Einstellung „pipefail“ aktiviert ist (mit set -o pipefail), dann ist der Exit-Status der Pipeline:
Eine Befehlsliste ist eine Folge von Pipelines, die durch ; (oder Zeilenumbruch), && oder || getrennt sind.
A; B ist eine Befehlsliste, bei der B ausgeführt wird, nachdem A abgeschlossen ist.A && B, wobei B nur ausgeführt wird, wenn A erfolgreich ist (mit Status null endet).A || B, wobei B nur ausgeführt wird, wenn A fehlschlägt (mit Status ungleich null endet).Der Exit-Status einer Befehlsliste ist der Exit-Status des letzten Befehls, der ausgeführt wurde.
Die Operatoren && und || lassen sich verketten, sodass der nächste Befehl abhängig vom Status der vorherigen Befehle ausgeführt wird.
Zum Beispiel
A && B && C && D || E
Setze diese logischen Operatoren sparsam ein. Sie können schnell zu unlesbarem Code oder zu Logik führen, die schwer zu verstehen ist.
Zum Beispiel könntest du denken, dass diese beiden dasselbe sind:
if A; then B; else C; fi
A && B || C
Der Unterschied ist: Wann wird C ausgeführt?
Hier sind ein paar Beispiele, in denen Befehlslisten den Code vereinfachen können.
Angenommen, du hast eine Datendatei mit Daten über Dreiecke, und ein Dreieck wird durch drei separate Zeilen dargestellt, die die Seiten des Dreiecks enthalten.
$ cat triangle.dat
3
4
5
9
12
14
Du kannst eine while-Schleife verwenden, bei der die Bedingung aus drei separaten read-Befehlen besteht:
while read a && read b && read c; do
if is_pythagorean "$a" "$b" "$c"; then
echo "$a:$b:$c is pythagorean"
else
echo "$a:$b:$c is not pythagorean"
fi
done < triangle.dat
Angenommen, is_pythagorean ist ein Befehl, der feststellt, ob die drei Seiten die Pythagoras-Gleichung erfüllen, dann wäre die Ausgabe:
3:4:5 is pythagorean
9:12:14 is not pythagorean
Viele Programmiersprachen kennen eine Form der Assertion, bei der eine Exception ausgelöst wird, wenn eine Bedingung fehlschlägt
assert(user == "Administrator", "wrong user");
Wir können in Bash einen OR-Operator verwenden, um diese Funktion nachzubilden:
die () { # a little convenience function
echo "$*" >&2
exit 1
}
[[ $user == "Administrator" ]] || die "wrong user"
[[ $password == "secret!" ]] || die "incorrect password"
Lange Befehlslisten werden schnell schwer zu lesen. Ein großzügiger Einsatz von Zeilenumbrüchen kann viel helfen.
Betrachte dieses Beispiel, in dem ein Wort zu einem Array hinzugefügt wird, wenn zwei Bedingungen erfüllt sind.
[[ "$word" != "$topic" ]] && [[ "$key" == "$(sorted "$topic")" ]] && anagrams+=("$candidate")
Bash erlaubt es dir, nach einer Pipe oder einem logischen Operator einen Zeilenumbruch einzufügen.
[[ "$word" != "$topic" ]] &&
[[ "$key" == "$(sorted "$topic")" ]] &&
anagrams+=("$candidate")
Allerdings kann der Operator am Zeilenende leicht übersehen werden. Mit einer Zeilenfortsetzung kannst du den Operator an den Anfang setzen, wodurch deutlicher wird, dass die Liste fortgesetzt wird:
[[ "$word" != "$topic" ]] \
&& [[ "$key" == "$(sorted "$topic")" ]] \
&& anagrams+=("$candidate")
Eine Zeilenfortsetzung ist die Zweizeichenfolge „Backslash“ und „Zeilenumbruch“ (\ + \n).
Wenn Bash diese Folge sieht, wird sie einfach aus dem Code entfernt und die aktuelle Zeile dadurch mit der nächsten Zeile fortgesetzt.
Achte darauf, dass zwischen dem Backslash und dem Zeilenumbruch keine Leerzeichen stehen.
Hier ein weiteres Beispiel
printf "%s\n" "${numbers[@]}" | bc --mathlib | sort --general-numeric-sort
oder
printf "%s\n" "${numbers[@]}" \
| bc --mathlib \
| sort --general-numeric-sort