È documentato (per POSIX) nella Sezione 2.9.1 Comandi semplici
delle specifiche di base di The Open Group. C'è un muro di testo lì; Indirizzo la tua attenzione sull'ultimo paragrafo:
Se esiste un nome comando, l'esecuzione deve continuare come descritto in Ricerca comandi ed esecuzione . Se non esiste un nome comando, ma il comando conteneva una sostituzione comando, il comando deve completare con lo stato di uscita dell'ultima sostituzione comando eseguita. In caso contrario, il comando deve essere completo con uno stato di uscita zero.
Quindi, per esempio,
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"
Ecco come funziona anche bash. Ma vedi anche la sezione "non così semplice" alla fine.
phk , nella sua domanda Le assegnazioni sono come comandi con uno stato di uscita tranne quando c'è una sostituzione di comando? , suggerisce
... sembra che un compito stesso valga come un comando ... con un valore di uscita zero, ma che si applica prima del lato destro del compito (ad esempio, una chiamata di sostituzione comando ...)
Non è un modo terribile di vederlo. Uno schema grezzo per determinare lo stato di restituzione di un semplice comando (non contenente ;
, &
, |
, &&
o ||
) è:
- Scansiona la linea da sinistra a destra fino a raggiungere la fine o una parola di comando (in genere un nome di programma).
- Se vedi un'assegnazione variabile, lo stato di ritorno per la riga potrebbe essere solo 0.
- Se vedi una sostituzione di comando - cioè,
$(…)
- prendi lo stato di uscita da quel comando.
- Se si raggiunge un comando effettivo (non in una sostituzione di comando), prendere lo stato di uscita da quel comando.
Lo stato di restituzione per la riga è l'ultimo numero che hai incontrato.
Le sostituzioni di comandi come argomenti del comando, ad esempio, foo $(bar)
non contano; si ottiene lo stato di uscita da foo
. Per parafrasare la notazione di phk , il comportamento qui è
temporary_variable = EXECUTE( "bar" )
overall_exit_status = EXECUTE( "foo", temporary_variable )
Ma questa è una leggera semplificazione eccessiva. Lo stato di restituzione generale da
A = $ ( cmd 1 ) B = $ ( cmd 2 ) C = $ ( cmd 3 ) D = $ ( cmd 4 ) E = mc 2
è lo stato di uscita da . L' assegnazione che si verifica dopo l' assegnazione non imposta lo stato di uscita generale su 0.
cmd4
E=
D=
icarus , nella sua risposta alla domanda di phk , solleva un punto importante: le variabili possono essere impostate come di sola lettura. Il terzo penultimo paragrafo della Sezione 2.9.1 della norma POSIX afferma:
Se una qualsiasi delle assegnazioni di variabili tenta di assegnare un valore a una variabile per la quale l' attributo readonly è impostato nell'attuale ambiente shell (indipendentemente dal fatto che l'assegnazione venga effettuata in quell'ambiente), si verificherà un errore di assegnazione variabile. Vedere le conseguenze degli errori della shell per le conseguenze di questi errori.
quindi se dici
readonly A
C=Garfield A=Felix T=Tigger
lo stato di ritorno è 1. Non importa se le stringhe Garfield
, Felix
e / o Tigger
sono sostituite con la (e) sostituzione (i) di comando - ma vedi le note sotto.
La Sezione 2.8.1 Conseguenze degli errori della shell ha un altro gruppo di testo e una tabella e termina con
In tutti i casi mostrati nella tabella in cui una shell interattiva non deve uscire, la shell non deve eseguire ulteriori elaborazioni del comando in cui si è verificato l'errore.
Alcuni dettagli hanno un senso; alcuni non lo fanno:
- A
A=
volte l'assegnazione interrompe la riga di comando, come sembra specificare quest'ultima frase. Nell'esempio sopra, C
è impostato su Garfield
, ma T
non è impostato (e, naturalmente, non lo è nemmeno A
).
- Allo stesso modo,
esegue
ma non . Ma, nelle mie versioni di bash (che includono 4.1.xe 4.3.X), esso non esegue . (Per inciso, ciò accresce ulteriormente l'interpretazione di phk secondo cui il valore di uscita del compito si applica prima del lato destro del compito.)
C=$(cmd1) A=$(cmd2) T=$(cmd3)
cmd1
cmd3
cmd2
Ma ecco una sorpresa:
Nelle mie versioni di bash,
di sola lettura A
C = qualcosa A = qualcosa T = qualcosa cmd 0
non eseguito. In particolare,cmd0
C = $ ( cmd 1 ) A = $ ( cmd 2 ) T = $ ( cmd 3 ) cmd 0
esegue
e , ma non . (Si noti che questo è l'opposto del suo comportamento quando non vi è alcun comando.) E imposta (oltre che ) nell'ambiente di . Mi chiedo se questo è un bug in bash.
cmd1
cmd3
cmd2
T
C
cmd0
Non così semplice:
Il primo paragrafo di questa risposta si riferisce a "comandi semplici".
Le specifiche dicono che
Un "comando semplice" è una sequenza di assegnazioni e reindirizzamenti variabili opzionali, in qualsiasi sequenza, facoltativamente seguita da parole e reindirizzamenti, terminata da un operatore di controllo.
Queste sono affermazioni come quelle nel mio primo esempio di blocco:
$ FOO=BAR
$ FOO=$(bar)
$ FOO=$(bar) baz
$ foo $(bar)
le prime tre includono assegnazioni di variabili e le ultime tre includono sostituzioni di comandi.
Ma alcune assegnazioni di variabili non sono così semplici.
bash (1) dice,
Istruzioni di assegnazione possono anche apparire come argomenti alias
, declare
, typeset
, export
, readonly
, e local
builtin comandi ( dichiarazione comandi).
Per export
, la specifica POSIX dice,
STATO DI USCITA
0Tutti gli operandi dei nomi sono stati esportati correttamente.
> 0Non è stato possibile esportare almeno un nome oppure è -p
stata specificata l' opzione e si è verificato un errore.
E POSIX non supporta local
, ma bash (1) dice:
È un errore da utilizzare local
quando non si è all'interno di una funzione. Lo stato di ritorno è 0 a meno che non local
venga utilizzato al di fuori di una funzione, viene fornito un nome non valido o il nome è una variabile di sola lettura.
Leggendo tra le righe, possiamo vedere che i comandi di dichiarazione piacciono
export FOO=$(bar)
e
local FOO=$(bar)
sono più simili
foo $(bar)
nella misura in cui essi ignorano lo stato di uscita da bar
e ti danno uno stato di uscita basata sul comando principale ( export
, local
, o foo
). Quindi abbiamo stranezze simili
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”)
con cui possiamo dimostrare
$ 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
e
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
Fortunatamente ShellCheck rileva l'errore e genera SC2155 , il che lo avvisa
export foo="$(mycmd)"
dovrebbe essere cambiato in
foo=$(mycmd)
export foo
e
local foo="$(mycmd)"
dovrebbe essere cambiato in
local foo
foo=$(mycmd)
local
lega a questo? Per esempiolocal foo=$(bar)
?