Was bedeutet set -e in einem Bash-Skript?

713

Ich untersuche den Inhalt dieser Preinst- Datei, die das Skript ausführt, bevor dieses Paket aus seiner Debian-Archivdatei (.deb) entpackt wird.

Das Skript hat den folgenden Code:

#!/bin/bash
set -e
# Automatically added by dh_installinit
if [ "$1" = install ]; then
   if [ -d /usr/share/MyApplicationName ]; then
     echo "MyApplicationName is just installed"
     return 1
   fi
   rm -Rf $HOME/.config/nautilus-actions/nautilus-actions.conf
   rm -Rf $HOME/.local/share/file-manager/actions/*
fi
# End automatically added section

Meine erste Frage betrifft die Zeile:

set -e

Ich denke, dass der Rest des Skripts ziemlich einfach ist: Es prüft, ob der Debian / Ubuntu-Paketmanager einen Installationsvorgang ausführt. Wenn dies der Fall ist, wird überprüft, ob meine Anwendung gerade auf dem System installiert wurde. Wenn dies der Fall ist , druckt das Skript die Meldung "MyApplicationName ist gerade installiert" und endet ( return 1bedeutet, dass dies mit einem "Fehler" endet, nicht wahr ?).

Wenn der Benutzer das Debian / Ubuntu-Paketsystem auffordert, mein Paket zu installieren, löscht das Skript auch zwei Verzeichnisse.

Ist das richtig oder fehlt mir etwas?

AndreaNobili
quelle
42
set -e
Anders Lindahl
46
Grund, warum Sie dies in Google nicht finden konnten: -e in Ihrer Abfrage wird als Negation interpretiert. Versuchen Sie folgende Abfrage: Bash Set "-e"
Maleev
3
@twalberg Als ich mir die gleiche Frage gestellt habe, habe ich mir angesehenman set
Sedat Kilinc
4
Wenn Sie suchen, wie Sie es ausschalten können, tauschen Sie den Bindestrich gegen ein Plus-Präfix:set +e
Tom Saleeba
@twalberg aber echte Leute zu fragen ist so viel interessanter als nur eine Anfrage von einem Roboter zu stellen ;-).
Vdegenne

Antworten:

797

Von help set:

  -e  Exit immediately if a command exits with a non-zero status.

Aber es wird von einigen als schlechte Praxis angesehen (Bash-FAQ- und IRC-Freenode-#Bash-FAQ-Autoren). Es wird empfohlen, Folgendes zu verwenden:

trap 'do_something' ERR

do_somethingFunktion ausführen , wenn Fehler auftreten.

Siehe http://mywiki.wooledge.org/BashFAQ/105

Gilles Quenot
quelle
14
Was wäre do_something, wenn ich die gleiche Semantik wie "Sofort beenden, wenn ein Befehl mit einem Status ungleich Null beendet wird" wollte?
CMCDragonkai
71
trap 'exit' ERR
Chepner
12
Die ERRFalle wird nicht von Shell - Funktionen geerbt, wenn Sie also Funktionen haben, set -o errtraceoder set -Ewerden Sie nur die Falle gestellt , damit einmal und es global gelten.
Ykay
31
nicht trap 'exit' ERRtun etwas anders set -e?
Andy
22
Wenn es eine schlechte Praxis ist, warum wird es dann in Debian-Paketen verwendet ?
Phuclv
98

set -eStoppt die Ausführung eines Skripts, wenn ein Befehl oder eine Pipeline einen Fehler aufweist. Dies ist das Gegenteil des Standardverhaltens der Shell, bei dem Fehler in Skripten ignoriert werden. Geben Sie help setein Terminal ein, um die Dokumentation zu diesem integrierten Befehl anzuzeigen.

Robin Green
quelle
44
Die Ausführung wird nur gestoppt, wenn der letzte Befehl in einer Pipeline einen Fehler aufweist. Es gibt eine Bash-spezifische Option, mit set -o pipefailder Fehler weitergegeben werden können, sodass der Rückgabewert des Pipeline-Befehls ungleich Null ist, wenn einer der vorhergehenden Befehle mit einem Status ungleich Null beendet wird.
Anthony Geoghegan
2
Beachten Sie, dass -o pipefaildies nur bedeutet, dass der Exit-Status des ersten Befehls ungleich Null (dh Fehler in -o errexitBegriffen) der Pipeline bis zum Ende weitergegeben wird. Die verbleibenden Befehle in der Pipeline noch laufen , auch mit set -o errexit. Zum Beispiel: echo success | cat - <(echo piping); echo continues, wo echo successeinen erfolgreichen darstellt, aber fehlbar Befehl, druckt success, pipingund continues, aber false | cat - <(echo piping); echo continuesmit , falsedie den Befehl jetzt erroring still, drucken noch pipingvor dem Verlassen.
bb010g
55

Wie pro bash - Das Set Builtin manuell, wenn -e/ errexitfestgelegt ist, die Shell beendet sofort , wenn eine Rohrleitung aus einem einzigen aus einfachen Befehl , eine Liste oder eine Verbindung Befehl zurückkehrt ein Nicht-Null - Status.

Standardmäßig ist der Exit-Status einer Pipeline der Exit-Status des letzten Befehls in der Pipeline, sofern die pipefailOption nicht aktiviert ist (standardmäßig deaktiviert).

In diesem Fall wird der Rückgabestatus der Pipeline des letzten (am weitesten rechts stehenden) Befehls mit einem Status ungleich Null oder Null, wenn alle Befehle erfolgreich beendet wurden.

Wenn Sie beim Beenden etwas ausführen möchten, definieren Sie trapbeispielsweise Folgendes :

trap onexit EXIT

Wo onexitist Ihre Funktion, um beim Beenden etwas zu tun, wie unten, wo die einfache Stapelverfolgung gedruckt wird :

onexit(){ while caller $((n++)); do :; done; }

Es gibt ähnliche Option -E/errtrace das würde Fall auf ERR statt, zum Beispiel:

trap onerr ERR

Beispiele

Beispiel für den Nullstatus:

$ true; echo $?
0

Beispiel für einen Status ungleich Null:

$ false; echo $?
1

Beispiele für das Negieren von Status:

$ ! false; echo $?
0
$ false || true; echo $?
0

Test mit pipefailDeaktivierung:

$ bash -c 'set +o pipefail -e; true | true | true; echo success'; echo $?
success
0
$ bash -c 'set +o pipefail -e; false | false | true; echo success'; echo $?
success
0
$ bash -c 'set +o pipefail -e; true | true | false; echo success'; echo $?
1

Test mit pipefailaktiviertem:

$ bash -c 'set -o pipefail -e; true | false | true; echo success'; echo $?
1
Kenorb
quelle
54

Ich habe diesen Beitrag gefunden, als ich versucht habe, den Exit-Status für ein Skript zu ermitteln, das aufgrund von a abgebrochen wurde set -e. Die Antwort erschien mir nicht offensichtlich; daher diese Antwort. Grundsätzlich wird set -edie Ausführung eines Befehls (z. B. eines Shell-Skripts) abgebrochen und der Exit-Statuscode des fehlgeschlagenen Befehls zurückgegeben (dh das innere Skript, nicht das äußere Skript) .

Angenommen, ich habe das Shell-Skript outer-test.sh:

#!/bin/sh
set -e
./inner-test.sh
exit 62;

Der Code für inner-test.shlautet:

#!/bin/sh
exit 26;

Wenn ich outer-script.shvon der Befehlszeile aus starte, endet mein äußeres Skript mit dem Exit-Code des inneren Skripts:

$ ./outer-test.sh
$ echo $?
26
entpnerd
quelle
10

Ich glaube, die Absicht ist, dass das betreffende Skript schnell fehlschlägt.

Um dies selbst zu testen, geben Sie einfach set -ean einer Bash-Eingabeaufforderung ein. Versuchen Sie jetzt zu laufen ls. Sie erhalten eine Verzeichnisliste. Geben Sie nun ein lsd. Dieser Befehl wird nicht erkannt und gibt einen Fehlercode zurück. Daher wird Ihre Bash-Eingabeaufforderung (aufgrund von set -e) geschlossen.

Um dies im Kontext eines 'Skripts' zu verstehen, verwenden Sie dieses einfache Skript:

#!/bin/bash 
# set -e

lsd 

ls

Wenn Sie es so ausführen, wie es ist, erhalten Sie die Verzeichnisliste aus lsder letzten Zeile. Wenn Sie das auskommentieren set -eund erneut ausführen, wird die Verzeichnisliste nicht angezeigt, da bash die Verarbeitung beendet, sobald der Fehler von auftritt lsd.

Kallin Nagelberg
quelle
Fügt diese Antwort Erkenntnisse oder Informationen hinzu, die in anderen Fragen noch nicht gegeben wurden?
Charles Duffy
6
Ich denke, es bietet eine klare, prägnante Erklärung der Funktionalität, die in den anderen Antworten nicht vorhanden ist. Nichts zusätzliches, nur fokussierter als die anderen Antworten.
Kallin Nagelberg
9

Dies ist eine alte Frage, aber keine der Antworten hier beschreibt die Verwendung von set -eaka set -o errexitin Debian-Paketsammlungsskripten. Die Verwendung dieser Option ist in diesen Skripten gemäß der Debian-Richtlinie obligatorisch . Die Absicht ist offenbar, die Möglichkeit eines unbehandelten Fehlerzustands zu vermeiden.

In der Praxis bedeutet dies, dass Sie verstehen müssen, unter welchen Bedingungen die von Ihnen ausgeführten Befehle einen Fehler zurückgeben können, und jeden dieser Fehler explizit behandeln müssen.

Häufige Fallstricke sind z. B. diff(gibt einen Fehler zurück, wenn es einen Unterschied gibt) und grep(gibt einen Fehler zurück, wenn keine Übereinstimmung vorliegt). Sie können die Fehler durch explizite Behandlung vermeiden:

diff this that ||
  echo "$0: there was a difference" >&2
grep cat food ||
  echo "$0: no cat in the food" >&2

(Beachten Sie auch, wie wir darauf achten, den Namen des aktuellen Skripts in die Nachricht aufzunehmen und Diagnosemeldungen anstelle der Standardausgabe in den Standardfehler zu schreiben.)

Wenn keine explizite Behandlung wirklich notwendig oder nützlich ist, tun Sie explizit nichts:

diff this that || true
grep cat food || :

(Die Verwendung des :No-Op-Befehls der Shell ist etwas unklar, wird aber häufig verwendet.)

Nur um es noch einmal zu wiederholen,

something || other

ist eine Abkürzung für

if something; then
    : nothing
else
    other
fi

dh wir sagen ausdrücklich, othersollte ausgeführt werden, wenn und nur wenn somethingfehlschlägt. Die Langschrift if(und andere Shell - Flusskontrolle Aussagen wie while, until) ist auch ein gültiger Weg , um einen Fehler zu behandeln (in der Tat, wenn es mit nicht, Shell - Skripte set -enie Flusskontrolle Aussagen enthalten könnte!)

Und nur um genau zu sein, würde in Abwesenheit eines solchen set -eHandlers das gesamte Skript sofort mit einem Fehler fehlschlagen, wenn diffein Unterschied festgestellt wird oder wenn grepkeine Übereinstimmung gefunden wird.

Andererseits erzeugen einige Befehle keinen Fehler-Exit-Status, wenn Sie dies wünschen. Häufig problematische Befehle sind find(der Exit-Status gibt nicht an, ob tatsächlich Dateien gefunden wurden) und sed(Der Exit-Status zeigt nicht an, ob das Skript Eingaben empfangen oder tatsächlich erfolgreich Befehle ausgeführt hat). In einigen Szenarien besteht eine einfache Wache darin, zu einem Befehl zu leiten, der schreit, wenn keine Ausgabe erfolgt:

find things | grep .
sed -e 's/o/me/' stuff | grep ^

Es ist zu beachten, dass der Exit-Status einer Pipeline der Exit-Status des letzten Befehls in dieser Pipeline ist. Die obigen Befehle maskieren also den Status von findund vollständig sedund sagen Ihnen nur, ob dies grepletztendlich erfolgreich war.

(Bash hat es natürlich getan set -o pipefail; Debian-Paketskripte können jedoch keine Bash-Funktionen verwenden. Die Richtlinie schreibt die Verwendung von POSIX shfür diese Skripte fest vor, obwohl dies nicht immer der Fall war.)

In vielen Situationen ist dies beim defensiven Codieren separat zu beachten. Manchmal müssen Sie z. B. eine temporäre Datei durchgehen, um zu sehen, ob der Befehl, der diese Ausgabe erzeugt hat, erfolgreich beendet wurde, selbst wenn Sie sonst aufgrund der Redewendung und Zweckmäßigkeit angewiesen werden, eine Shell-Pipeline zu verwenden.

Tripleee
quelle
Dies ist eine ausgezeichnete Antwort. und es fördert die beste Praxis. Ich hatte genau das gleiche Problem mit dem GREP-Befehl und wollte das 'set -e'
Minnie
7
Script 1: without setting -e
#!/bin/bash
decho "hi"
echo "hello"
This will throw error in decho and program continuous to next line

Script 2: With setting -e
#!/bin/bash
set -e
decho "hi" 
echo "hello"
# Up to decho "hi" shell will process and program exit, it will not proceed further
Manikandan Raj
quelle