Was bedeutet a! vor einem Befehl in der Shell?

72

Die Frage steht im Titel. Was ist der Zweck eines Shell-Befehls (Teil eines Shell-Skripts), der mit einem Ausrufezeichen beginnt? Konkretes Beispiel:

In foo.sh:

#!/usr/bin/env bash
set -e
! docker stop foo
! docker rm -f foo
# ... other stuff

Ich weiß, dass ohne das Leerzeichen das Ausrufezeichen zum Ersetzen des Verlaufs verwendet wird und ! <expression>laut Manpage verwendet werden kann, um " Wahr, wenn Ausdruck falsch ist " zu bewerten . Aber im Beispielkontext macht das für mich keinen Sinn.

sthzg
quelle
8
Wenn der Rückkehrcode (sichtbar als $?) 0 (Erfolg) war, wird er auf 1 umgedreht. Wenn der Rückgabecode ein Fehler war (nicht Null), wird er auf 0 (Null) umgedreht. Vielleicht wird erwartet, dass diese Befehle fehlschlagen und Sie haben -eoder gesetzt set -o errexit? Wie auch immer, dies ist ein Fall, in dem Kommentare hätten hinzugefügt werden müssen.
Cdarke
1
@cdarke set -eignoriert Befehle, deren Exit-Status explizit mit negiert wird !.
Chepner

Antworten:

77

TL; DR: Hiermit wird nur das set -eFlag in der Zeile umgangen, in der Sie es verwenden.


Hinzufügen von add zu hek2mgls korrekter und nützlicher Antwort .

Du hast:

set -e
! command

Bash Referenzhandbuch → Pipelines beschreibt:

Jeder Befehl in einer Pipeline wird in einer eigenen Subshell ausgeführt. Der Exit-Status einer Pipeline ist der Exit-Status des letzten Befehls in der Pipeline (...). Wenn das reservierte Wort '!' Vor der Pipeline ist der Exit-Status die logische Negation des Exit-Status, wie oben beschrieben. Die Shell wartet, bis alle Befehle in der Pipeline beendet sind, bevor ein Wert zurückgegeben wird.

Dies bedeutet, dass das !Voranstellen eines Befehls dessen Beendigungsstatus negiert:

$ echo 23
23
$ echo $?
0
                # But
$ ! echo 23
23
$ echo $?
1

Oder:

$ echo 23 && echo "true" || echo "fail"
23
true
$ ! echo 23 && echo "true" || echo "fail"
23
fail

Der Exit-Status ist in vielerlei Hinsicht nützlich. In Ihrem Skript set -ewird das Skript zusammen mit verwendet, wenn ein Befehl einen Status ungleich Null zurückgibt.

Also, wenn Sie haben:

set -e
command1
command2

Wenn command1ein Status ungleich Null zurückgegeben wird, wird das Skript beendet und nicht fortgesetzt command2.

Es gibt jedoch auch einen interessanten Punkt zu erwähnen, der in 4.3.1 The Set Builtin beschrieben wird :

-e

Beenden Sie das Programm sofort, wenn eine Pipeline (siehe Pipelines), die aus einem einzelnen einfachen Befehl (siehe Einfache Befehle), einer Liste (siehe Listen) oder einem zusammengesetzten Befehl (siehe Zusammengesetzte Befehle) bestehen kann, einen Status ungleich Null zurückgibt. Die Shell wird nicht beendet, wenn der fehlgeschlagene Befehl unmittelbar nach einer Weile oder bis zum Schlüsselwort Teil der Befehlsliste ist, Teil des Tests in einer if-Anweisung, Teil eines Befehls, der in einem && oder || ausgeführt wird Liste mit Ausnahme des Befehls nach dem letzten && oder ||, eines Befehls in einer Pipeline außer dem letzten oder wenn der Rückgabestatus des Befehls mit invertiert wird! . Wenn ein anderer zusammengesetzter Befehl als eine Subshell einen Status ungleich Null zurückgibt, weil ein Befehl fehlgeschlagen ist, während -e ignoriert wurde, wird die Shell nicht beendet. Ein Trap auf ERR wird, falls gesetzt, ausgeführt, bevor die Shell beendet wird.


Berücksichtigen Sie all dies, wenn Sie:

set -e
! command1
command2

Was Sie tun, ist, die set -eFlagge in der zu umgehen command1. Warum?

  • Wenn es command1ordnungsgemäß ausgeführt wird, wird der Status Null zurückgegeben. !wird es negieren, aber set -ekeinen Exit durch das auslösen, da es von einem mit! invertierten Rückgabestatus stammt, wie oben beschrieben.
  • Wenn dies command1fehlschlägt, wird ein Status ungleich Null zurückgegeben. !wird es negieren, so dass die Zeile am Ende einen Nullstatus zurückgibt und das Skript normal fortgesetzt wird.
fedorqui 'SO hör auf zu schaden'
quelle
31
Mit set -ebedeutet dies , dass im Skript kein Fehler zulässig ist. Mit einem führenden !Zeichen wird jedoch eine Ausnahme markiert, was bedeutet, dass dieser Befehl in Ordnung ist.
Ryenus
5
@ryenus Nicht wirklich. !negiert einfach den Exit-Status eines Befehls. Dies ist nur eine der vielen Ausnahmen in der Liste der Befehle, deren Beendigungsstatus set -eignoriert wird.
Chepner
6
@chepner: Ich bin mir nicht sicher, warum du "Nicht wirklich" sagst. Ryenus 'Kommentar ist richtig.
Keith Thompson
1
@ryenus Ja, ich habe. !negiert den Exit-Status eines Befehls, unabhängig davon, ob die -eOption aktiviert ist oder nicht . Es markiert nicht einfach einen Befehl als Ausnahme von der -eOption.
Chepper
2
@chpner !negiert in der Tat allein den Befehls-Exit-Status, unabhängig davon, ob er set -egesetzt ist oder nicht. Aber hier geht es um den Nebeneffekt von !im Kontext vonset -e , der das Skript nicht ausfällt, selbst wenn der Befehl erfolgreich war. Ohne solche Nebenwirkung. Das Skript schlägt fehl, wenn der Beendigungsstatus eines erfolgreichen Befehls von negiert wird !.
Ryenus
24

Wenn Sie nicht möchten, dass das Skript in beiden Fällen fehlschlägt, Fehler oder Erfolg des Befehls, können Sie auch diese Alternative verwenden:

set -e
docker stop foo || true

Der boolesche Wert oder true bewirkt , dass die Pipeline immer 0den Rückgabewert hat.

hek2mgl
quelle
1
man bashsagt wenn das reservierte Wort! Vor einer Pipeline ist der Exit-Status dieser Pipeline die logische Negation des Exit-Status . Würde dies nicht bedeuten, dass der Befehl bei erfolgreicher Ausführung einen Fehler auslöst, da er ein True negiert?
Fedorqui 'SO hör auf,'
Ja, ich benutze nicht die!
hek2mgl
@fedorqui Ich habe dasselbe erwartet, aber wenn ich es mit einem einfachen Demo-Skript versuche, wird es nicht beendet, wenn der Befehl erfolgreich ist. zB set -e\n! echo "I am okay"\n\echo "done"läuft durch. set -e\n! give_me_an_error\n\echo "done"läuft auch durch (dies ist, was in der Antwort beschrieben wird). Zumindest in meiner Shell (zsh unter macOS) ist es daher fehleranfällig und beeinträchtigt erfolgreiche exit 0Befehle nicht.
sthzg
12
@sthzg OK Ich habe es gefunden. Aus dem Bash-Referenzhandbuch → 4.3.1 Das Set Builtin on -e: Die Shell wird nicht beendet, wenn (...) der Rückgabestatus des Befehls mit invertiert wird! .
Fedorqui 'SO hör auf,'
1
@ Fedorqui Guter Fang und gut zu wissen! Ich empfehle, das in eine Antwort zu setzen
hek2mgl