Wie wird der Rückgabestatus einer Variablenzuordnung ermittelt?

10

Ich habe Konstrukte in Skripten wie diesen gesehen:

if somevar="$(somecommand 2>/dev/null)"; then
...
fi

Ist das irgendwo dokumentiert? Wie wird der Rückgabestatus einer Variablen ermittelt und in welcher Beziehung steht er zur Befehlssubstitution? (Würde ich zum Beispiel das gleiche Ergebnis erzielen if echo "$(somecommand 2>/dev/null)"; then?)

Platzhalter
quelle

Antworten:

12

Es ist (für POSIX) in Abschnitt 2.9.1 Einfache Befehle der Open Group Base-Spezifikationen dokumentiert . Dort gibt es eine Textwand; Ich lenke Ihre Aufmerksamkeit auf den letzten Absatz:

Wenn ein Befehlsname ist, wird die Ausführung wird wie beschrieben weiter in Befehl Suchen und Ausführung . Wenn kein Befehlsname vorhanden ist, der Befehl jedoch eine Befehlsersetzung enthielt, muss der Befehl mit dem Beendigungsstatus der zuletzt durchgeführten Befehlsersetzung abgeschlossen werden. Andernfalls wird der Befehl mit einem Null-Exit-Status abgeschlossen.

So zum Beispiel

   Command                                         Exit Status
$ FOO=BAR                                   0 (but see also the note from icarus, below)
$ FOO=$(bar)                                Exit status from "bar"
$ FOO=$(bar) baz                            Exit status from "baz"
$ foo $(bar)                                Exit status from "foo"

So funktioniert Bash auch. Siehe aber auch den Abschnitt „nicht so einfach“ am Ende.

phk , in seiner frage Zuweisungen sind wie Befehle mit einem Exit-Status, außer wenn es eine Befehlsersetzung gibt? , schlägt vor

… Es sieht so aus, als ob eine Zuweisung selbst als Befehl zählt… mit einem Exit-Wert von Null, der jedoch vor der rechten Seite der Zuweisung gilt (z. B. ein Befehlsersetzungsaufruf…)

Das ist keine schreckliche Sichtweise. Ein grobes Schema des Rückgabestatus eines einfachen Befehl zur Bestimmung (ein nicht enthält ;, &, |, &&oder ||) ist:

  • Scannen Sie die Zeile von links nach rechts, bis Sie das Ende oder ein Befehlswort (normalerweise einen Programmnamen) erreichen.
  • Wenn Sie eine Variablenzuweisung sehen, ist der Rückgabestatus für die Zeile möglicherweise 0.
  • Wenn Sie eine Befehlsersetzung sehen, dh $(…)den Exit-Status dieses Befehls übernehmen.
  • Wenn Sie einen tatsächlichen Befehl erreichen (nicht in einer Befehlsersetzung), übernehmen Sie den Exit-Status dieses Befehls.
  • Der Rückgabestatus für die Zeile ist die letzte Nummer, auf die Sie gestoßen sind.
    Befehlsersetzungen als Argumente an den Befehl, zum Beispiel foo $(bar), nicht zählen; Sie erhalten den Exit-Status von foo. Um die Notation von phk zu paraphrasieren , ist das Verhalten hier

    temporary_variable  = EXECUTE( "bar" )
    overall_exit_status = EXECUTE( "foo", temporary_variable )

Dies ist jedoch eine leichte Vereinfachung. Der allgemeine Rückgabestatus von

A = $ ( cmd 1 ) B = $ ( cmd 2 ) C = $ ( cmd 3 ) D = $ ( cmd 4 ) E = mc 2
ist der Exit-Status von . Die Zuweisung, die nach der Zuweisung erfolgt, setzt den Gesamt-Exit-Status nicht auf 0.cmd4E=D=

icarus wirft in seiner antwort auf die frage von phk einen wichtigen punkt auf: variablen können als schreibgeschützt gesetzt werden. Der vorletzte Absatz in Abschnitt 2.9.1 des POSIX-Standards lautet:

Wenn eine der Variablenzuweisungen versucht, einer Variablen, für die das schreibgeschützte Attribut in der aktuellen Shell-Umgebung festgelegt ist , einen Wert zuzuweisen (unabhängig davon, ob die Zuweisung in dieser Umgebung erfolgt), tritt ein Variablenzuweisungsfehler auf. Die Konsequenzen dieser Fehler finden Sie unter Folgen von Shell- Fehlern.

Also, wenn du sagst

readonly A
C=Garfield A=Felix T=Tigger

der Rückgabestatus ist 1. Es spielt keine Rolle , ob die Saiten Garfield, Felixund / oder Tigger mit Kommandosubstitution (en) ersetzt - aber Hinweise weiter unten sehen.

Abschnitt 2.8.1 Folgen von Shell-Fehlern enthält einen weiteren Textbündel und eine Tabelle und endet mit

In allen in der Tabelle aufgeführten Fällen, in denen eine interaktive Shell nicht beendet werden muss, darf die Shell den Befehl, in dem der Fehler aufgetreten ist, nicht weiter verarbeiten.

Einige Details sind sinnvoll; manche nicht:

  • Die A=Zuweisung bricht manchmal die Befehlszeile ab, wie der letzte Satz zu spezifizieren scheint. Im obigen Beispiel Cist auf gesetzt Garfield, aber Tnicht gesetzt (und natürlich auch nicht  A).
  • Ebenso wird ausgeführt, aber nicht . Aber in meinen bash - Versionen (die 4.1.X und 4.3.X enthalten), ist es nicht ausführen . (Dies wirkt sich übrigens weiter auf die Interpretation von phk aus, dass der Exit-Wert der Zuweisung vor der rechten Seite der Zuweisung gilt.)C=$(cmd1) A=$(cmd2) T=$(cmd3)cmd1cmd3
    cmd2

Aber hier ist eine Überraschung:

In meinen Versionen von Bash,

schreibgeschützt A.
C = etwas A = etwas T = etwas  cmd 0

wird ausgeführt. Im Speziellen,cmd0

C = $ ( cmd 1 ) A = $ ( cmd 2 ) T = $ ( cmd 3 )    cmd 0
führt aus und , aber nicht . (Beachten Sie, dass dies das Gegenteil von seinem Verhalten ist, wenn kein Befehl vorhanden ist.) Und es setzt (sowie ) in der Umgebung von . Ich frage mich, ob dies ein Fehler in Bash ist.cmd1cmd3cmd2TCcmd0


Nicht so einfach:

Der erste Absatz dieser Antwort bezieht sich auf „einfache Befehle“.  Die Spezifikation sagt,

Ein „einfacher Befehl“ ist eine Folge von optionalen Variablenzuweisungen und -umleitungen in beliebiger Reihenfolge, optional gefolgt von Wörtern und Umleitungen, die von einem Steuerungsoperator beendet werden.

Dies sind Anweisungen wie in meinem ersten Beispielblock:

$ FOO=BAR
$ FOO=$(bar)
$ FOO=$(bar) baz
$ foo $(bar)

Die ersten drei enthalten Variablenzuweisungen, und die letzten drei enthalten Befehlsersetzungen.

Einige Variablenzuweisungen sind jedoch nicht ganz so einfach.  Bash (1) sagt:

Zuweisungsanweisungen können auch als Argumente an die erscheinen alias, declare, typeset, export, readonly, und localbuiltin Befehle ( Erklärung Befehle).

Für export, die POSIX - Spezifikation sagt,

STATUS BEENDEN

    0
      Alle Namensoperanden wurden erfolgreich exportiert.
    > 0
      Mindestens ein Name konnte nicht exportiert werden oder die -pOption wurde angegeben und ein Fehler ist aufgetreten.

Und POSIX unterstützt nicht local, aber bash (1) sagt:

Es ist ein Fehler, localwenn Sie sich nicht in einer Funktion befinden. Der Rückgabestatus ist 0, es localsei denn, er wird außerhalb einer Funktion verwendet, ein ungültiger Name wird angegeben oder name ist eine schreibgeschützte Variable.

Wenn wir zwischen den Zeilen lesen, können wir sehen, dass Deklarationsbefehle wie

export FOO=$(bar)

und

local FOO=$(bar)

sind eher wie

foo $(bar)

soweit sie den Exit - Status von ignorieren bar und gibt Ihnen einen Exit - Status auf Basis des Hauptbefehl ( export, localoder foo). Also haben wir Verrücktheit wie

   Command                                           Exit Status
$ FOO=$(bar)                                    Exit status from "bar"
                                                  (unless FOO is readonly)
$ export FOO=$(bar)                             0 (unless FOO is readonly,
                                                  or other error from “export”)
$ local FOO=$(bar)                              0 (unless FOO is readonly,
                                                  statement is not in a function,
                                                  or other error from “local”)

mit denen wir demonstrieren können

$ export FRIDAY=$(date -d tomorrow)
$ echo "FRIDAY   = $FRIDAY, status = $?"
FRIDAY   = Fri, May 04, 2018  8:58:30 PM, status = 0
$ export SATURDAY=$(date -d "day after tomorrow")
date: invalid date ‘day after tomorrow’
$ echo "SATURDAY = $SATURDAY, status = $?"
SATURDAY = , status = 0

und

myfunc() {
    local x=$(echo "Foo"; true);  echo "x = $x -> $?"
    local y=$(echo "Bar"; false); echo "y = $y -> $?"
    echo -n "BUT! "
    local z; z=$(echo "Baz"; false); echo "z = $z -> $?"
}

$ myfunc
x = Foo -> 0
y = Bar -> 0
BUT! z = Baz -> 1

Glücklicherweise fängt ShellCheck den Fehler ab und löst SC2155 aus , was darauf hinweist

export foo="$(mycmd)"

sollte geändert werden zu

foo=$(mycmd)
export foo

und

local foo="$(mycmd)"

sollte geändert werden zu

local foo
foo=$(mycmd)
G-Man sagt "Reinstate Monica"
quelle
1
Grosses Dankeschön! Wissen Sie, wie sich Bonuspunkte darauf localauswirken? ZB local foo=$(bar)?
Wildcard
1
Für das zweite Beispiel (nur FOO=$(bar)) ist es erwähnenswert, dass theoretisch sowohl der Exit-Status als auch die Zuweisung eine Rolle spielen könnten, siehe unix.stackexchange.com/a/341013/117599
phk
1
@ Wildcard: Ich bin froh, dass es dir gefällt. Ich habe es gerade noch einmal aktualisiert. Ein großer Teil der Version, die Sie gerade gelesen haben, war falsch. Was denkst du, solange du hier bist? Ist dies ein Fehler in Bash, A=foo cmdder cmdauch dann ausgeführt Awird, wenn er schreibgeschützt ist?
G-Man sagt "Reinstate Monica"
1
@phk: (1) Interessante Theorie, aber ich bin mir nicht sicher, wie es Sinn macht. Schauen Sie sich das Beispiel kurz vor meiner Überschrift „Überraschung“ noch einmal an. Wenn Aschreibgeschützt ist, wird der Befehl gesetzt C=value₁ A=value₂ T=value₃, Caber nicht T(und natürlich Anicht gesetzt) ​​- die Shell hat die Verarbeitung der Befehlszeile beendet und ignoriert T=value₃, da dies A=value₂ein Fehler ist. (2) Danke für den Link zur Stack Overflow- Frage - ich habe Kommentare dazu gepostet.
G-Man sagt "Reinstate Monica"
1
@Wildcard "Für Bonuspunkte wissen Sie, wie lokale Verbindungen dazu bestehen?". Ja ... local=$(false)hat einen Exit-Wert, 0weil (von der Manpage) : It is an error to use local when not within a function. The return status is 0 unless local is used outside a function, an invalid name is supplied, or name is a readonly variable.. Dies ist nicht genug Trollface auf der Welt für das Genie, das es so entworfen hat.
David Tonhofer
2

Es ist in Bash ( LESS=+/'^SIMPLE COMMAND EXPANSION' bash) dokumentiert :

Wenn nach der Erweiterung noch ein Befehlsname übrig ist .... Andernfalls wird der Befehl beendet. ... Wenn keine Befehlsersetzungen vorhanden waren, wird der Befehl mit dem Status Null beendet.

Mit anderen Worten (meine Worte):

Wenn nach der Erweiterung kein Befehlsname mehr vorhanden ist und keine Befehlsersetzungen ausgeführt wurden, wird die Befehlszeile mit dem Status Null beendet.

phk
quelle