Linux-Tutorial / Wie die Shell Befehle ausführt: ;, &&, ||, Aliase und Umgebungsvariablen

Bash liest einen Befehl, löst seinen Namen und seine Argumente auf, führt Erweiterungen und Umleitungen durch und führt ihn dann aus. Ein Semikolon ; führt den nächsten Befehl unabhängig vom vorherigen Ergebnis aus, && führt ihn nur nach Erfolg aus, und || führt ihn nur nach einem Nicht-Null-Status aus. Aliase ändern den Befehlstext, während die Shell ihn liest; Umgebungsvariablen übergeben Einstellungen an Kindprozesse. Diese Unterschiede erklären, warum ähnlich aussehende Befehlszeilen sich unterschiedlich verhalten können.

Beginnen Sie mit Variablen, Umgebungsvariablen, Aliasen, Exit-Status und dem grundlegenden &&/||-Fluss. Befehlsgruppen, Pipelines, Hintergrundarbeit und das vollständige Skript sind optionale Vertiefungen. Das erste Bash-Skript setzt diese Regeln in eine Datei um; Bash-Fehlerbehandlung behandelt Fehler, Pfad- und Grenzen bei Nebenläufigkeit.

Wie wird ein Befehlsname aufgelöst?

Die Beispiele setzen Bash voraus. Die Shell liest die Befehlszeile, behandelt Argumente, Variablen und Umleitungen und löst den Befehlsnamen auf. Ein Alias, eine Funktion, ein Shell-Builtin oder eine über PATH gefundene ausführbare Datei kann unterschiedliches Verhalten erzeugen. Überprüfen Sie die Auflösung der aktuellen Shell mit:

type -a printf
type -a ls
command -v ls

type -a kann Aliase, Funktionen, eingebaute Befehle und ausführbare Kandidaten anzeigen. command -v prüft, ob ein Name aufgelöst werden kann. PATH listet Verzeichnisse auf, die auf externe ausführbare Dateien durchsucht werden; es erklärt nicht jeden Alias oder jede Funktion als einen Dateipfad. Überschreiben Sie nicht das PATH Ihres Systems für diese Übung.

Lesen Sie den Exit-Status und Ausführungssymbole

Ein Befehl hinterlässt einen Exit-Status. Null zeigt Erfolg an; ein nicht null Wert zeigt einen Fehler oder ein anderes vom Befehl definiertes Ergebnis an. Zum Beispiel kann grep 1 zurückgeben, wenn nichts übereinstimmt. Lesen Sie $? sofort oder speichern Sie es in einer Variablen, da ein anderer Befehl den zuletzt gespeicherten Status überschreibt.

true
printf 'status=%s\n' "$?"
false
printf 'status=%s\n' "$?"

Die Ausgabe ist status=0, gefolgt von status=1. Die bedingte Ausführung verwendet den Status, nicht ob eine Nachricht erfolgreich aussieht. Bash gibt 127 für einen Befehl zurück, den sie nicht finden kann, und 126 für einen Befehl, den sie findet, aber nicht ausführen kann. Die Signalbeendigung hat ihre eigene Statuskonvention; gehen Sie nicht davon aus, dass jeder Fehler 1 zurückgibt.

Symbol Zweck
; Führe den nächsten Befehl unabhängig vom Erfolg oder Misserfolg aus
&& Führe den nächsten Befehl nur nach Erfolg aus
|| Führe den nächsten Befehl nur nach einem ungleichen Null-Status aus
& Führe asynchron aus, ohne zu warten
| Verbinde die Standardausgabe mit der nächsten Standardeingabe
|& Standardausgabe und Standardfehler an die nächste Standardeingabe weiterleiten
! Den Status eines Befehls oder einer Pipeline logisch invertieren

Die Bash-Listen-Dokumentation klassifiziert ;, &, && und || als Listenoperatoren. | und |& bilden Pipelines, während ! ein reserviertes Wort ist, das den Pipeline-Status negiert. Führen Sie diese Beispiele in Bash mit true, false, printf, grep, cat und sleep aus, die normalerweise auf Ubuntu und Rocky Linux verfügbar sind. Es sind keine Administratorrechte erforderlich.

Verbinden Sie Befehle je nach Erfolg oder Misserfolg

Trennen Sie Sequenzierung von bedingter Ausführung

; wartet auf den vorhergehenden Befehl und fährt dann unabhängig von dessen Ergebnis fort. Eine neue Zeile trennt auch Befehle in normalen Listen. Anführungszeichen, Backslashes, Pipelines oder unvollständige zusammengesetzte Befehle können dazu führen, dass Bash weiterliest, anstatt die neue Zeile als Ende eines Befehls zu behandeln.

false; printf 'always\n'
false && printf 'not run\n'
false || printf 'fallback\n'
true && printf 'next step\n'

Die Ausgabe ist always, fallback, dann next step; not run wird übersprungen. && und || verwenden Kurzschlussauswertung: die rechte Seite wird übersprungen, wenn ihre Bedingung nicht erfüllt ist. Das Muster cd /actual/work/path && work_command verhindert, dass ein fehlgeschlagener Verzeichniswechsel die Arbeit im falschen Verzeichnis ausführt. Ersetzen Sie diesen illustrativen Pfad und Befehl durch echte, vorbereitete Werte.

Unterscheiden Sie die Auswertungsreihenfolge von einem if-Zweig

&& und || haben die gleiche Priorität und verknüpfen sich von links nach rechts. In A && B || C kann C ausgeführt werden, auch wenn A erfolgreich ist, weil B fehlschlagen kann.

true && false || printf 'fallback runs\n'

Dies druckt fallback runs. Verwenden Sie if, wenn nur die ursprüngliche Bedingung einen von zwei Zweigen auswählen soll.

if true; then
  false
else
  printf 'condition failed\n'
fi

Es gibt keine Ausgabe: Das fehlgeschlagene false innerhalb des ausgewählten then-Zweigs schaltet die Ausführung nicht in else um. Dieses Versagen muss in einem Produktionsskript immer noch bewusst gehandhabt werden; die Wahl des richtigen Zweigs stellt die fehlgeschlagene Arbeit nicht wieder her.

Fehler melden, ohne den ursprünglichen Status zu verlieren

false || printf 'failure reported\n'
printf 'list status=%s\n' "$?"

Die Ausgabe ist failure reported, dann list status=0. || bedeutet nicht automatisch „Fehler ignorieren“, aber das erfolgreiche printf wird zum zuletzt ausgeführten Befehl und gibt der Liste einen Null-Status. Speichern und geben Sie das ursprüngliche Versagen mit exit zurück, wenn das Skript nach der Meldung fehlschlagen muss.

Befehle in einer Einheit gruppieren

Geschweifte Klammern laufen in der aktuellen Shell

{ ...; } gruppiert Befehle in der aktuellen Shell. Trennen Sie die geschweiften Klammern von den Befehlen durch Leerzeichen und setzen Sie ein Semikolon oder einen Zeilenumbruch vor die schließende Klammer.

LESSON_SCOPE=outer
{ LESSON_SCOPE=inner; printf '%s\n' "$LESSON_SCOPE"; }
printf '%s\n' "$LESSON_SCOPE"
unset LESSON_SCOPE

Dies gibt inner zweimal aus, da die Variablenänderung in der aktuellen Shell bleibt. Das letzte unset entfernt die Übungsvariable.

Klammern verwenden eine Subshell

LESSON_SCOPE=outer
( LESSON_SCOPE=inner; printf '%s\n' "$LESSON_SCOPE" )
printf '%s\n' "$LESSON_SCOPE"
unset LESSON_SCOPE

Dies gibt zunächst inner und dann outer aus. Klammern erstellen eine Subshell-Umgebung, sodass gewöhnliche Variablenzuweisungen und Verzeichnisänderungen in der äußeren Shell nicht bestehen bleiben. Dies macht externe Effekte wie Dateiänderungen oder Dienstanforderungen nicht rückgängig. Siehe Bash-Befehlsgruppierung für die Unterscheidung.

Verwenden Sie Pipes, Standardfehler und Statusumkehr

Unterscheiden Sie eine Pipe von einer Bedingung

printf 'alpha\nbeta\n' | grep 'alpha'

Nur alpha wird ausgegeben. | verbindet die vorherige Standardausgabe mit der nächsten Standardeingabe. Es wählt keinen Befehl nach einem Fehler aus, wie || es tut. Pipeline-Befehle verarbeiten verbundene Ströme; sie sind keine Abfolge von schrittweisen Erfolgsvoraussetzungen.

Beziehen Sie Standardfehler mit |& ein

bash -c 'printf "normal\n"; printf "error\n" >&2' |& cat

Sowohl normal als auch error passieren durch cat. In diesem Beispiel kombiniert Bash |& den Standardfehler mit der Standardausgabe wie 2>&1 |. Wenn andere Umleitungen vorhanden sind, ist ihre Reihenfolge wichtig; ersetzen Sie die Syntax nicht blind. Ein nachgelagertes tee kann den Stream anzeigen und protokollieren, aber die beiden Arten von Ausgabe wurden dann kombiniert.

! negiert den Status statt der Ausgabe

! false
printf 'inverted status=%s\n' "$?"

Dies druckt inverted status=0: das fehlerhafte false wird in Erfolg für die Bedingungsprüfung negiert. Ein ursprünglicher Nullstatus wird zu einem ungleich null. Ausgabetext und Dateiinhalte werden nicht invertiert.

Überprüfen Sie Pipeline-Fehler mit pipefail

( set +o pipefail; false | cat; printf 'default=%s\n' "$?" )
( set -o pipefail; false | cat; printf 'pipefail=%s\n' "$?" )

Die Ausgabe ist default=0, dann pipefail=1. Eine Pipeline gibt normalerweise den Status des letzten Befehls zurück, sodass ein erfolgreicher cat den vorhergehenden false verbergen kann. Bei pipefail ist der Status nur dann null, wenn alle Befehle erfolgreich sind; andernfalls stammt er vom rechts außen liegenden fehlgeschlagenen Befehl. Klammern isolieren beide Vergleiche, sodass sie die Optionen der äußeren Shell nicht ändern. Die Bash-Pipeline-Dokumentation beschreibt diese Regeln.

Einen Hintergrundjob starten und seinen Abschluss prüfen

& startet die asynchrone Ausführung, ohne auf die Fertigstellung zu warten. Es unterscheidet sich von &&, das nur nach erfolgreichem Abschluss fortfährt. Dieses Beispiel startet einen kurzen Hintergrundjob und wartet dann absichtlich auf dessen Fertigstellung.

sleep 2 &
lesson_job=$!
printf 'foreground continues\n'
wait "$lesson_job"
printf 'job status=%s\n' "$?"
unset lesson_job

foreground continues erscheint zuerst, gefolgt von job status=0 nach Abschluss. $! identifiziert den zuletzt gestarteten Hintergrundprozess. Das unmittelbare $? nach dem Start zeigt nicht an, ob der Job letztendlich erfolgreich war. Interaktives Bash stellt jobs, fg und bg bereit, um Jobs zu inspizieren und zu verschieben. Allein das Anhängen von & garantiert kein Fortbestehen nach dem Logout; wählen Sie passende Sitzungs- oder Servicemanagementmethoden für länger laufende Arbeiten.

Üben Sie den Ablauf mit einem vollständigen Skript

Das vollständige Beispiel unten simuliert den Build, das Testen und die Bereitstellung mit Nachrichten. Es führt keinen tatsächlichen Build, keine Bereitstellung und keine Dateilöschung durch. Speichern Sie es als neue Datei mit dem Namen course15-flow.sh in einem leeren Übungsverzeichnis. Nur Bash wird benötigt; Sie müssen die Datei nicht ausführbar machen.

#!/usr/bin/env bash
build() { printf 'build\n'; }
run_tests() { printf 'test\n'; return "${1:-0}"; }
deploy() { printf 'deploy\n'; }

build && run_tests "${1:-0}" && deploy
lesson_status=$?
if (( lesson_status != 0 )); then
  printf 'workflow failed: status=%s\n' "$lesson_status" >&2
fi
exit "$lesson_status"
bash course15-flow.sh 0
echo $?
bash course15-flow.sh 1
echo $?

Der erste Durchlauf gibt build, test und deploy aus und gibt anschließend 0 zurück. Wenn Sie das Argument auf 1 ändern, werden nur build und test ausgegeben, der Fehler wird auf den Standardfehler gemeldet, und es wird 1 zurückgegeben. Führen Sie es erneut mit 0 aus, um den erfolgreichen Pfad wiederherzustellen. ${1:-0} verwendet Null, wenn das erste Argument fehlt oder leer ist; verwenden Sie für diese Übung nur Null oder Eins.

Eine Funktion, die mit name() { ...; } definiert ist, benennt eine Befehlsgruppe; return liefert deren Status. Das sofortige Speichern des Workflow-Status in lesson_status verhindert, dass die Meldung die ursprüngliche Fehlfunktion ersetzt. In realen Abläufen sollten vorbereitete, validierte Aufgaben substituiert und jeder Fehler explizit behandelt werden.

Verwenden Sie Semikolons für kurze, unabhängige Schritte, && für erfolgsabhängige Arbeiten und if plus Funktionen für Verzweigungen und Wiederherstellung. work; cleanup führt eine Bereinigung nach einem normalen Arbeitsfehler aus, garantiert dies jedoch nicht, wenn die Shell frühzeitig beendet wird oder ein Signal erhält. Entwerfen Sie eine solche Bereinigung mit Einrichtungen wie trap. Ebenso behandelt set -e nicht automatisch jeden Fehler: Bedingungen und UND/ODER-Listen haben Ausnahmen, daher sollten wichtige Stati explizit überprüft werden.

Aliase werden erweitert, wenn Bash einen Befehl liest

alias erstellt einen kurzen Befehlsnamen, der häufig in interaktivem Bash verwendet wird. Geben Sie die folgende Definition ein und führen Sie dann ll oder type ll an einer neuen Eingabeaufforderung aus. Bash liest und analysiert Befehle, bevor sie ausgeführt werden, daher kann das Definieren und Verwenden eines Alias im selben zusammengesetzten Befehl möglicherweise nicht wie erwartet erweitert werden.

alias ll='ls -l'
type ll
ll

Ein Alias wirkt normalerweise nur auf die aktuelle Shell und wird in nichtinteraktivem Bash nicht erweitert, es sei denn, er wird separat aktiviert. Lassen Sie ein Automatisierungsskript nicht versehentlich von einem interaktiven Alias abhängen. Entfernen Sie diesen Übungs-Alias mit unalias ll. Für wiederverwendbare Operationen, die Argumente benötigen, sollten Sie eher eine Shell-Funktion anstelle eines Alias in Betracht ziehen.

Unterscheiden Sie Shell-Variablen von der Umgebung

Eine Shell-Variable ist in der aktuellen Shell verfügbar. Sobald sie exportiert wird, wird sie auch in die Umgebung von anschließend gestarteten Kindprozessen übernommen. Verwenden Sie einen ausschließlich zu Übungszwecken bestimmten Namen anstelle einer gängigen Systemvariable:

LESSON_MODE=practice
printf '%s\n' "$LESSON_MODE"
export LESSON_MODE
printenv LESSON_MODE

Beide Ausgaben sind practice. Die erste stammt aus der Expansion in der aktuellen Shell; die zweite wird aus der an den externen printenv-Prozess übergebenen Umgebung gelesen. Das Exportieren ändert keine bereits laufende andere Shell oder einen übergeordneten Prozess rückwirkend.

Um einen Wert nur für einen Befehl bereitzustellen, verwenden Sie NAME=value command. Wenn Sie fertig sind, führen Sie unset LESSON_MODE in der aktuellen Shell aus. Verwenden Sie Ihre Home-Verzeichnis- oder Systemvariable PATH nicht als Übungsnamen.

Fehlerbehebung bei der Ausführungsreihenfolge

  • Wenn Sie nicht wissen, wie ein Name aufgelöst wird, beginnen Sie mit type -a name.
  • Wenn bedingte Ausführung Sie überrascht, überprüfen Sie den Exit-Status jeder Stufe einzeln.
  • Wenn ein Alias fehlschlägt, prüfen Sie, ob Bash interaktiv ist und ob Sie ihn auf einer neuen Befehlszeile nach der Definition verwendet haben.
  • Wenn ein untergeordneter Befehl eine Variable nicht sehen kann, prüfen Sie, ob sie exportiert wurde.

Sequenzierungsoperatoren, Aliase und Variablen beeinflussen alle die Ausführung, sind aber nicht austauschbar. Bevor Sie Befehle verbinden, die Dateien ändern, testen Sie den Ablauf mit nebenwirkungsarmen eingebauten Befehlen wie true, false und printf.

Offizielle Dokumentation

Die GNU Bash Listen-Dokumentation behandelt ;, && und ||. Die Bash Aliase-Dokumentation behandelt die Erweiterungszeitpunkte. Für Variablen und das Verhalten der Umgebung siehe das Bash Handbuch.

Für detaillierte Regeln konsultieren Sie die GNU Bash-Dokumentation zu Pipelines und pipefail, Befehlsgruppierung und Exit-Status.