webentwicklung-frage-antwort-db.com.de

Beenden Sie den Code für die Zuweisung von Variablen zur Befehlsersetzung in Bash

Ich bin verwirrt darüber, welchen Fehlercode der Befehl zurückgibt, wenn eine Variablenzuweisung einfach und mit Befehlsersetzung ausgeführt wird:

a=$(false); echo $?

Es gibt 1 aus, was mich zu der Überzeugung bringt, dass die Zuweisung der Variablen beim letzten nicht durchläuft oder einen neuen Fehlercode erzeugt. Aber als ich das ausprobierte:

false; a=""; echo $?

Es gibt 0 aus, offensichtlich gibt a="" dies zurück und überschreibt 1, der von false zurückgegeben wird.

Ich möchte wissen, warum dies geschieht. Gibt es irgendwelche Besonderheiten bei der Variablenzuweisung, die sich von anderen normalen Befehlen unterscheiden? Oder einfach nur, weil a=$(false) als ein einzelner Befehl betrachtet wird und nur der Befehlssubstitutionsteil sinnvoll ist?

- UPDATE -

Vielen Dank an alle, aus den Antworten und Kommentaren habe ich den Punkt erhalten "Wenn Sie eine Variable mithilfe der Befehlsersetzung zuweisen, ist der Beendigungsstatus der Status des Befehls." (von @Barmar), diese Erklärung ist hervorragend klar und leicht verständlich, aber sprechen Sie für Programmierer nicht genau genug. Ich möchte die Referenz dieses Punktes von Behörden wie TLDP oder der Manpage GNU sehen Hilf mir, es herauszufinden, danke nochmal!

41
Reorx

Wenn ein Befehl als $(command) ausgeführt wird, kann sich die Ausgabe des Befehls selbst ersetzen .

Wenn du sagst:

a=$(false)             # false fails; the output of false is stored in the variable a

die vom Befehl false erzeugte Ausgabe wird in der Variablen a gespeichert. Darüber hinaus ist der Exit-Code derselbe wie der vom Befehl erzeugte. help false würde sagen:

false: false
    Return an unsuccessful result.

    Exit Status:
    Always fails.

Auf der anderen Seite sagen:

$ false                # Exit code: 1
$ a=""                 # Exit code: 0
$ echo $?              # Prints 0

bewirkt, dass der Exit-Code für die Zuweisung zu a zurückgegeben wird, der 0 ist.


BEARBEITEN:

Zitat aus dem Handbuch :

Wenn eine der Erweiterungen eine Befehlsersetzung enthielt, ist der Beendigungsstatus des Befehls der Beendigungsstatus der zuletzt ausgeführten Befehlsersetzung.

Zitat aus BASHFAQ/002 :

Wie kann ich den Rückgabewert und/oder die Ausgabe eines Befehls in einer Variablen speichern?

...

output=$(command)

status=$?

Die Zuweisung zu output hat keine Auswirkung auf den Beendigungsstatus von command, der sich noch in $? befindet.

57
devnull

Beachten Sie, dass dies nicht der Fall ist, wenn Sie local innerhalb einer Funktion verwenden. Das Verhalten unterscheidet sich geringfügig von dem in der akzeptierten Antwort beschriebenen Verhalten und dem hier veröffentlichten Link: http://mywiki.wooledge.org/BashFAQ/002

Nehmen Sie zum Beispiel dieses Bash-Skript:

#!/bin/bash
function funWithLocalVar() {
    local output="$(echo "Doing some stuff.";exit 1)"
    local exitCode=$?
    echo "output: $output"
    echo "exitCode: $exitCode"
}

function funWithoutLocalVar() {
    output="$(echo "Doing some stuff.";exit 1)"
    local exitCode=$?
    echo "output: $output"
    echo "exitCode: $exitCode"
}

funWithLocalVar
funWithoutLocalVar

Hier ist die Ausgabe davon:

[email protected]:~$ ./tmp.sh 
output: Doing some stuff.
exitCode: 0
output: Doing some stuff.
exitCode: 1

Vielleicht kümmert es niemanden, aber ich habe es getan. Ich brauchte eine Minute, um herauszufinden, warum mein Statuscode immer 0 war, wenn es manchmal nicht der Fall war. Nicht zu 100% klar warum. Aber nur zu wissen, dass dies geholfen hat.

6
Nick P.

Ich bin gestern (29. August 2018) auf dasselbe Problem gestoßen.

Zusätzlich zu local in Nick Ps Antwort und @ sevkos Kommentar in der akzeptierten Antwort , hat declare im globalen Bereich dasselbe Verhalten.

Hier ist mein Bash-Code:

#!/bin/bash

func1()
{
    ls file_not_existed
    local local_ret1=$?
    echo "local_ret1=$local_ret1"

    local local_var2=$(ls file_not_existed)
    local local_ret2=$?
    echo "local_ret2=$local_ret2"

    local local_var3
    local_var3=$(ls file_not_existed)
    local local_ret3=$?
    echo "local_ret3=$local_ret3"
}

func1

ls file_not_existed
global_ret1=$?
echo "global_ret1=$global_ret1"

declare global_var2=$(ls file_not_existed)
global_ret2=$?
echo "global_ret2=$global_ret2"

declare global_var3
global_var3=$(ls file_not_existed)
global_ret3=$?
echo "global_ret3=$global_ret3"

Die Ausgabe:

$ ./declare_local_command_substitution.sh 2>/dev/null 
local_ret1=2
local_ret2=0
local_ret3=2
global_ret1=2
global_ret2=0
global_ret3=2

Beachten Sie die Werte von local_ret2 und global_ret2 in der obigen Ausgabe. Die Exit-Codes werden von local und declare überschrieben.

Meine Bash-Version:

$ echo $BASH_VERSION 
4.4.19(1)-release
1
Zhi Zhu

(keine Antwort auf die ursprüngliche Frage, aber zu lang für einen Kommentar)

Beachten Sie, dass export A=$(false); echo $? 0 ausgibt! Anscheinend gelten die in devnulls Antwort zitierten Regeln nicht mehr. So fügen Sie diesem Zitat ein wenig Kontext hinzu (Hervorhebung meines):

3.7.1 Einfache Befehlserweiterung

...

Wenn nach der Erweiterung noch ein Befehlsname vorhanden ist, wird die Ausführung wie unter unten beschrieben ausgeführt. Andernfalls wird der Befehl beendet. Wenn eine der Erweiterungen eine Befehlsersetzung enthielt, ist der Beendigungsstatus des Befehls der Beendigungsstatus der zuletzt durchgeführten Befehlsersetzung. Wenn keine Befehlssubstitutionen vorhanden waren, wird der Befehl mit dem Status Null beendet.

3.7.2 Befehlssuche und Ausführung [- dies ist der Fall "unter"]

IIUC beschreibt das Handbuch var=foo als Sonderfall der var=foo command...-Syntax (ziemlich verwirrend!). Die Regel "Beendigungsstatus der letzten Befehlssubstitution" gilt nur für den Fall ohne Befehl. 

Es ist zwar verführerisch, export var=foo als "modifizierte Zuweisungssyntax" zu betrachten, es ist jedoch nicht - export ist ein eingebauter Befehl (der zufällig zuweisungsähnliche Argumente benötigt).

=> Wenn Sie den Ersetzungsstatus eines var AND-Erfassungsbefehls exportieren möchten, führen Sie ihn in zwei Schritten aus:

A=$(false)
# ... check $?
export A

Dieser Weg funktioniert auch im set -e-Modus - wird sofort beendet, wenn die Befehlsersetzung nicht 0 zurückgibt.