Tracks
/
Bash
Bash
/
Lehrplan
/
Pipelines und Befehlslisten
Pi

Pipelines und Befehlslisten in Bash

{one: "1 Übung", other: "%{count} Übungen"}

Über Pipelines und Befehlslisten

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.

Eingabe und Ausgabe

Bevor wir anfangen, eine kurze Einführung in Eingabe und Ausgabe.

Prozesse haben „Standard-I/O-Kanäle“.

  • Ein Prozess kann auf „stdin“ Eingaben lesen.
  • Ein Prozess kann auf „stdout“ Ausgaben schreiben.
  • Ein Prozess kann auf „stderr“ Fehlerausgaben schreiben.

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.

Pipelines

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.

Advanced
  • 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:

    • null, wenn alle Befehle erfolgreich sind, andernfalls
    • der letzte Exit-Status ungleich null der Befehle in der Pipeline.

Befehlslisten

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
  • B wird ausgeführt, wenn A erfolgreich ist,
  • C wird ausgeführt, wenn A und B erfolgreich sind,
  • D wird ausgeführt, wenn A, B und C erfolgreich sind,
  • E wird ausgeführt, wenn einer von A, B, C oder D fehlschlägt.
Caution

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?

  • Im ersten Schnipsel (der if-Anweisung) wird C nur ausgeführt, wenn A fehlschlägt.
  • Im zweiten Schnipsel wird C ausgeführt, wenn A fehlschlägt oder wenn A erfolgreich ist, aber B fehlschlägt!

Wozu Befehlslisten nützlich sind

Hier sind ein paar Beispiele, in denen Befehlslisten den Code vereinfachen können.

Blöcke von Zeilen aus einer Datei lesen

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
Assertions

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"

Stilüberlegungen

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")
Note

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
Über GitHub bearbeiten Der Link öffnet sich in einem neuen Fenster oder Tab