Come viene determinato lo stato di ritorno di un'assegnazione variabile?


10

Ho visto costrutti in script come questo:

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

Questo è documentato da qualche parte? Come viene determinato lo stato di ritorno di una variabile e come si collega alla sostituzione dei comandi? (Ad esempio, otterrei lo stesso risultato con if echo "$(somecommand 2>/dev/null)"; then?)

Risposte:


12

È 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.cmd4E=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, Felixe / 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 Tnon è 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)cmd1cmd3
    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.cmd1cmd3cmd2TCcmd0


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 localbuiltin comandi ( dichiarazione comandi).

Per export, la specifica POSIX dice,

STATO DI USCITA

    0
      Tutti gli operandi dei nomi sono stati esportati correttamente.
    > 0
      Non è stato possibile esportare almeno un nome oppure è -pstata specificata l' opzione e si è verificato un errore.

E POSIX non supporta local, ma bash (1) dice:

È un errore da utilizzare localquando non si è all'interno di una funzione. Lo stato di ritorno è 0 a meno che non localvenga 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)

1
Ottimo grazie! Per i punti bonus, sai come si locallega a questo? Per esempio local foo=$(bar)?
Wildcard

1
Per il secondo esempio (solo FOO=$(bar)) vale la pena notare che teoricamente sia lo stato di uscita che l'assegnazione potrebbero avere un ruolo, vedi unix.stackexchange.com/a/341013/117599
phk

1
@Wildcard: sono contento che ti piaccia. L'ho appena aggiornato di nuovo; gran parte della versione che hai appena letto era sbagliata. Finché sei qui, cosa ne pensi? È un bug in bash, che A=foo cmdfunziona cmdanche se Aè di sola lettura?
G-Man dice "Ripristina Monica" il

1
@phk: (1) Teoria interessante, ma non sono sicuro di come abbia senso. Dai un'occhiata all'esempio appena prima della mia rubrica "sorpresa". Se Aè di sola lettura, il comando C=value₁ A=value₂ T=value₃imposta Cma non T(e, ovviamente, Anon è impostato) - la shell ha terminato l'elaborazione della riga di comando, ignorando T=value₃, perché si A=value₂tratta di un errore. (2) Grazie per il link alla domanda Stack Overflow : ho pubblicato i suoi commenti.
G-Man dice "Ripristina Monica" il

1
@Wildcard "Per i punti bonus, sai come legami locali in questo?". Sì ... local=$(false)ha valore di uscita 0, perché (dalla pagina man): 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.. Questo non è abbastanza trollface sul mondo per il genio che lo ha progettato in quel modo.
David Tonhofer,

2

È documentato in Bash ( LESS=+/'^SIMPLE COMMAND EXPANSION' bash):

Se è rimasto un nome comando dopo l'espansione .... Altrimenti, il comando termina. ... Se non vi sono sostituzioni di comandi, il comando esce con uno stato zero.

In altre parole (le mie parole):

Se dopo l'espansione non è rimasto alcun nome di comando e non sono state eseguite sostituzioni di comandi, la riga di comando esce con uno stato zero.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.