Stato dell'uscita Bash utilizzato con PIPE


10

Sto cercando di capire come viene comunicato lo stato di uscita quando viene utilizzata una pipe. Supponiamo che sto usando whichper individuare un programma inesistente:

which lss
echo $?
1

Poiché whichnon è stato possibile individuare, lssho ottenuto uno stato di uscita di 1. Questo va bene. Tuttavia, quando provo quanto segue:

which lss | echo $?
0

Ciò indica che l'ultimo comando eseguito è uscito normalmente. L'unico modo per capirlo è che forse PIPE produce anche uno stato di uscita. È questo il modo giusto di capirlo?


2
Tutti i componenti di una pipeline vengono eseguiti contemporaneamente . Pertanto, in which lss | echo $?, echoviene avviato prima che whichsia terminato; la shell che genera la riga di comando echonon ha modo di sapere quale sarà lo stato di uscita whichin seguito.
Charles Duffy,

Non corrono allo stesso tempo. Lo scopo del pipe è reindirizzare l'output di un comando nell'altro. L'ultimo comando che la shell ha eseguito (cioè l'ultimo sulla tua istruzione) sarà quello che restituirà un codice di ritorno.
Tim S.

3
@TimS. Ma funzionano allo stesso tempo, è così che puoi ottenere i primi bit di output prima che il primo comando sia terminato se è un comando molto lungo.
Casuale 832,

Immagino che stavo pensando all'inizio dell'esecuzione - c'è una certa sovrapposizione, ma i processi non iniziano insieme. Capisco cosa intendi, errore mio. (La pipe reindirizza l'output, non l'esecuzione ...) Ho sbagliato i miei commenti, ma spero che tu sappia cosa intendo. :)
Tim S.

sì, ma l'inizio dell'esecuzione non ha importanza, l'importante è la fine; l'ultimo comando inizia prima della fine del primo
user371366

Risposte:


14

Lo stato di uscita della pipeline è lo stato di uscita dell'ultimo comando nella pipeline (a meno che l'opzione shell non pipefailsia impostata nelle shell che la supportano, nel qual caso lo stato di uscita sarà quello dell'ultimo comando nella pipeline che esce con uno stato diverso da zero).

Non è possibile generare lo stato di uscita di una pipeline dall'interno di una pipeline, poiché non esiste alcun modo per sapere quale valore sarebbe fino a quando la pipeline non avrà effettivamente terminato l'esecuzione.

La pipeline

which lss | echo $?

è privo di senso poiché echonon legge il suo input standard (le pipeline vengono utilizzate per passare i dati tra l'output di un comando e quello successivo). Il echonon sarebbe anche stampare lo stato di uscita del gasdotto, ma lo stato di uscita del comando eseguito immediatamente prima che la pipeline.

$ false
$ which hello | echo $?
1
which: hello: Command not found.

$ true
$ which hello | echo $?
0
which: hello: Command not found.

Questo è un esempio migliore:

$ echo hello | read a
$ echo $?
0

$ echo nonexistent | { read filename && rm $filename; }
rm: nonexistent: No such file or directory
$ echo $?
1

Ciò significa che una pipeline può anche essere utilizzata con if:

if gzip -dc file.gz | grep -q 'something'; then
  echo 'something was found'
fi

10

Lo stato di uscita di una pipe è lo stato di uscita del comando di destra. Lo stato di uscita del comando a sinistra viene ignorato.

(Notare che which lss | echo $?non lo mostra. Si eseguirà which lss | true; echo $?per mostrarlo. In which lss | echo $?, echo $?riporta lo stato dell'ultimo comando prima di questa pipeline.)

Il motivo per cui le shell si comportano in questo modo è che esiste uno scenario abbastanza comune in cui un errore nella parte sinistra dovrebbe essere ignorato. Se il lato destro esce (o più generalmente chiude il suo input standard) mentre il lato sinistro sta ancora scrivendo, il lato sinistro riceve un segnale SIGPIPE. In questo caso, di solito non c'è nulla di sbagliato: il lato destro non si preoccupa dei dati; se il ruolo del lato sinistro è solo quello di produrre questi dati, allora è giusto che si fermi.

Tuttavia, se il lato sinistro muore per qualche motivo diverso da SIGPIPE, o se il compito del lato sinistro non era solo quello di produrre dati sull'output standard, un errore sul lato sinistro è un vero errore che dovrebbe essere segnalato.

In semplice sh, l'unica soluzione è usare una pipe con nome.

set -e
mkfifo p
command1 >p & pid1=$!
command2 <p
wait $pid1

In ksh, bash e zsh, è possibile indicare alla shell di uscire da una pipeline con uno stato diverso da zero se qualsiasi componente della pipeline esce con uno stato diverso da zero. Devi impostare l' pipefailopzione:

  • ksh: set -o pipefail
  • bash: shopt -s pipefail
  • zsh: setopt pipefail(o setopt pipe_fail)

In mksh, bash e zsh, è possibile ottenere lo stato di ogni componente della pipeline utilizzando la variabile PIPESTATUS(bash, mksh) o pipestatus(zsh), che è un array contenente lo stato di tutti i comandi nell'ultima pipeline (una generalizzazione di $?).


Grazie Gilles, non ero a conoscenza di queste complessità coinvolte. Questo mi ha chiarito ulteriormente questo aspetto.
sshekhar1980,

4

Sì, PIPE produce uno stato di uscita; se si desidera il codice di uscita del comando prima del PIPE, è possibile utilizzare$PIPESTATUS

In base a queste domande e risposte di Stack Overflow , per stampare lo stato di uscita di un comando quando si utilizza una pipe è possibile:

your_command | echo $PIPESTATUS

Oppure imposta pipefail con il comando set -o pipefail(per annullarlo, eseguilo set +o pipefail) e usa $?invece di $PIPESTATUS.

your_command | echo $?

Questi comandi funzionano solo dopo averli eseguiti almeno una volta; ciò significa PIPESTATUSche funziona solo quando lo esegui dopo il comando con la pipe, in questo modo:

command_which_exit_10 | command_which_exit_1
echo "${PIPESTATUS[0]} ${PIPESTATUS[1]}"

Riceverai 10 per ${PIPESTATUS[0]}e 1 per ${PIPESTATUS[1]}. Vedi queste domande e risposte per ulteriori informazioni.


2
Mentre PIPESTATUScontiene gli stati di uscita dei comandi nell'ultima pipeline, il tuo primo esempio non ha più senso di somecmd | echo $?, che Kusalananda ha affrontato nella loro risposta. false; true | echo $PIPESTATUSstampa 1per lo stato di uscita di false.
ilkkachu,

Riconoscimento per PIPESTATUS e pipefiail, ma |exhoè davvero confuso
diamine

0

La shell valuta la riga di comando prima dell'esecuzione della pipeline. Quindi $?è il valore dell'ultimo comando eseguito prima della pipeline. Non importa echose si è avviato prima o dopo which.

Ora, se la tua domanda era specificamente sulla propagazione dello stato di uscita, puoi studiare il comportamento predefinito come questo:

% false | true
% echo $?
0

% true | false
% echo $?
1

Come puoi vedere, lo stato di uscita di una pipeline è lo stato di uscita del suo ultimo comando.

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.