Le assegnazioni sono come comandi con uno stato di uscita tranne quando c'è una sostituzione di comando?


10

Vedi i seguenti esempi e i loro output nelle shell POSIX:

  1. false;echo $?oppure false || echo 1:1
  2. false;foo="bar";echo $?oppure foo="bar" && echo 0:0
  3. foo=$(false);echo $?oppure foo=$(false) || echo 1:1
  4. foo=$(true);echo $?oppure foo=$(true) && echo 0:0

Come menzionato dalla risposta più votata su /programming/6834487/what-is-the-variable-in-shell-scripting :

$? viene utilizzato per trovare il valore restituito dell'ultimo comando eseguito.

Questo è probabilmente un po 'fuorviante in questo caso, quindi otteniamo la definizione POSIX che è anche citata in un post da quel thread:

? Si espande allo stato di uscita decimale della pipeline più recente (consultare Pipeline).

Quindi sembra che un compito stesso valga come un comando (o piuttosto una parte della pipeline) con un valore di uscita pari a zero ma che si applica prima del lato destro dell'assegnazione (ad esempio, la sostituzione del comando chiama i miei esempi qui).

Vedo come questo comportamento abbia senso dal punto di vista pratico, ma mi sembra in qualche modo insolito che il compito stesso contasse in quell'ordine. Forse per chiarire perché è strano per me, supponiamo che il compito sia una funzione:

ASSIGNMENT( VARIABLE, VALUE )

allora foo="bar"sarebbe

ASSIGNMENT( "foo", "bar" )

e foo=$(false)sarebbe qualcosa di simile

ASSIGNMENT( "foo", EXECUTE( "false" ) )

il che significherebbe che EXECUTEviene eseguito prima e solo dopo ASSIGNMENT viene eseguito, ma è ancora lo EXECUTEstato che conta qui.

Sono corretto nella mia valutazione o sto fraintendendo / mancando qualcosa? Sono questi i motivi giusti per me considerare questo comportamento "strano"?


1
Scusa, ma non mi è chiaro cosa trovi strano.
Kusalananda

1
@Kusalananda Forse ti aiuta a dirti che è iniziato con me che mi chiedevo: "Perché false;foo="bar";echo $?restituisce sempre 0 quando è stato l'ultimo vero comando eseguito false?" Fondamentalmente, i compiti si comportano in modo speciale quando si tratta di codici di uscita. Il loro codice di uscita è sempre 0, tranne quando non è dovuto a qualcosa che è stato eseguito come parte del lato destro del compito.
phk,

Risposte:


10

Lo stato di uscita per le assegnazioni è strano . Il modo più ovvio per un'assegnazione non riuscita è se la variabile target è contrassegnata readonly.

$ err(){ echo error ; return ${1:-1} ; }
$ PS1='$? $ '
0 $ err 42
error
42 $ A=$(err 12)
12 $ if A=$(err 9) ; then echo wrong ; else E=$? ; echo "E=$E ?=$?" ; fi
E=9 ?=0
0 $ readonly A
0 $ if A=$(err 10) ; then echo wrong ; else E=$? ; echo "E=$E ?=$?" ; fi
A: is read only
1 $

Si noti che né i percorsi veri né falsi dell'istruzione if sono stati adottati, l'assegnazione non riuscita ha interrotto l'esecuzione dell'intera istruzione. bash in modalità POSIX e ksh93 e zsh interromperanno tutti uno script se un compito fallisce.

Per citare lo standard POSIX su questo :

Un comando senza un nome di comando, ma che include una sostituzione di comando, ha uno stato di uscita dell'ultima sostituzione di comando eseguita dalla shell.

Questa è esattamente la parte della grammatica della shell coinvolta

 foo=$(err 42)

che proviene da un simple_command(comando_semplice → cmd_prefix → ASSIGNMENT_WORD). Pertanto, se un'assegnazione ha esito positivo, lo stato di uscita è zero, a meno che non fosse coinvolta la sostituzione del comando, nel qual caso lo stato di uscita è lo stato dell'ultima. Se l'assegnazione non riesce, lo stato di uscita è diverso da zero, ma potresti non essere in grado di rilevarlo.


1
Per aggiungere alla tua risposta, ecco una risposta da un thread diverso in cui viene citato un nuovo standard POSIX su questo, la conclusione è sostanzialmente la stessa: unix.stackexchange.com/a/270831/117599
phk

4

Tu dici,

... 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. Ma è 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=

Inoltre, come sottolinea Icarus , le variabili possono essere impostate come di sola lettura. Considera la seguente variazione sull'esempio di Icaro:

$ err() { echo "stdout $*"; echo "stderr $*" >&2; return ${1:-1}; }
$ readonly A
$ Z=$(err 41 zebra) A=$(err 42 antelope) B=$(err 43 badger)
stderr 41 zebra
stderr 42 antelope
bash: A: readonly variable
$ echo $?
1
$ printf "%s = %s\n" Z "$Z" A "$A" B "$B"
Z = stdout 41 zebra
A =
B =
$

Anche se Aè di sola lettura, bash esegue la sostituzione del comando a destra di A=- e quindi interrompe il comando perché Aè di sola lettura. Ciò contraddice ulteriormente la tua interpretazione secondo cui il valore di uscita del compito si applica prima del lato destro del compito.

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.