Estado de salida de bash utilizado con PIPE

10

Estoy tratando de entender cómo se comunica el estado de salida cuando se usa una tubería. Supongamos que estoy usando whichpara localizar un programa inexistente:

which lss
echo $?
1

Como whichno lsspude localizar , obtuve un estado de salida de 1. Esto está bien. Sin embargo, cuando intento lo siguiente:

which lss | echo $?
0

Esto indica que el último comando ejecutado ha salido normalmente. La única forma en que puedo entender esto es que quizás PIPE también produce un estado de salida. ¿Es esta la forma correcta de entenderlo?

sshekhar1980
fuente
2
Todos los componentes de una tubería se ejecutan al mismo tiempo . Así, en which lss | echo $?, echose inicia antes de que whichhaya salido; el shell que genera la línea de comando echono tiene forma posible de saber cuál whichserá el estado de salida de más adelante.
Charles Duffy
No corren al mismo tiempo. Todo el propósito de la tubería es redirigir la salida de un comando al otro. El último comando que ejecutó el shell (es decir, el último en su declaración) será el que devuelva un código de retorno.
Tim S.
3
@TimS. Pero se ejecutan al mismo tiempo, así es como puede obtener los primeros bits de salida antes de que termine el primer comando si es un comando de ejecución muy larga.
Random832
Supongo que estaba pensando en el inicio de la ejecución: hay una superposición definitiva, pero los procesos no comienzan juntos. Aunque veo lo que quieres decir, mi error. (La tubería está redirigiendo la salida, no la ejecución ...) Fallé mis comentarios, pero espero que sepas a qué me refiero. :)
Tim S.
sí, pero el inicio de la ejecución no importa, lo importante es el final; el último comando comienza antes de que termine el primero
user371366

Respuestas:

14

El estado de salida de la tubería es el estado de salida del último comando en la tubería (a menos que la opción de shell pipefailse establezca en los shells que lo admiten, en cuyo caso el estado de salida será el del último comando en la tubería que sale con un estado distinto de cero).

No es posible generar el estado de salida de una tubería dentro de una tubería, ya que no hay forma de saber cuál sería ese valor hasta que la tubería haya terminado de ejecutarse.

La tubería

which lss | echo $?

no tiene sentido ya echoque no lee su entrada estándar (las tuberías se utilizan para pasar datos entre la salida de un comando a la entrada del siguiente). El echotampoco habría imprimir el estado de salida de la tubería, pero el estado de salida del comando se ejecute inmediatamente antes de la tubería.

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

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

Este es un mejor ejemplo:

$ echo hello | read a
$ echo $?
0

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

Esto significa que una tubería también se puede usar con if:

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

El estado de salida de una tubería es el estado de salida del comando de la derecha. El estado de salida del comando de la izquierda se ignora.

(Tenga en cuenta que which lss | echo $?no muestra esto. Usted correría which lss | true; echo $?para mostrar esto. En which lss | echo $?, echo $?informa el estado del último comando antes de esta canalización).

La razón por la cual los shells se comportan de esta manera es que hay un escenario bastante común en el que se debe ignorar un error en el lado izquierdo. Si el lado derecho sale (o más generalmente cierra su entrada estándar) mientras el lado izquierdo todavía está escribiendo, entonces el lado izquierdo recibe una señal SIGPIPE. En este caso, generalmente no hay nada de malo: el lado derecho no se preocupa por los datos; Si el papel del lado izquierdo es únicamente para producir estos datos, entonces está bien que se detenga.

Sin embargo, si el lado izquierdo muere por algún motivo que no sea SIGPIPE, o si el trabajo del lado izquierdo no era únicamente producir datos en la salida estándar, entonces un error en el lado izquierdo es un error genuino que debe ser reportado

En simple sh, la única solución es usar una tubería con nombre.

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

En ksh, bash y zsh, puede indicar al shell que realice una salida de canalización con un estado distinto de cero si algún componente de la canalización sale con un estado distinto de cero. Necesitas configurar la pipefailopción:

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

En mksh, bash y zsh, puede obtener el estado de cada componente de la tubería utilizando la variable PIPESTATUS(bash, mksh) o pipestatus(zsh), que es una matriz que contiene el estado de todos los comandos de la última tubería (una generalización de $?)

Gilles 'SO- deja de ser malvado'
fuente
Gracias Gilles, no estaba al tanto de estas complejidades involucradas. Esto realmente me lo aclaró más.
sshekhar1980
4

Sí, PIPE produce un estado de salida; si desea el código de salida del comando antes del PIPE, puede usar$PIPESTATUS

De acuerdo con estas preguntas y respuestas sobre el desbordamiento de pila , para imprimir el estado de salida de un comando cuando utiliza una tubería, puede hacer lo siguiente:

your_command | echo $PIPESTATUS

O configure pipefail con el comando set -o pipefail(para desarmarlo, hacer set +o pipefail), y use en $?lugar de $PIPESTATUS.

your_command | echo $?

Estos comandos solo funcionan después de ejecutarlos al menos una vez; eso significa que PIPESTATUSsolo funciona cuando lo ejecutas después del comando con la tubería, así:

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

Obtendrá 10 por ${PIPESTATUS[0]}y 1 por ${PIPESTATUS[1]}. Consulte estas preguntas y respuestas de U&L para obtener más información.

jeremy.Snidaro
fuente
2
Si bien PIPESTATUScontiene los estados de salida de los comandos en la última tubería, su primer ejemplo no tiene más sentido que el somecmd | echo $?que Kusalananda trató en su respuesta. false; true | echo $PIPESTATUSimprime 1para el estado de salida de false.
ilkkachu
Upvote para PIPESTATUS y pipefiail, pero los |exhoque realmente está confundiendo
Eckes
0

El shell evalúa la línea de comando antes de ejecutar la tubería. Entonces, ¿ $?es el valor del último comando ejecutado antes de la canalización? Si echose inicia antes o después whiches irrelevante.

Ahora, si su pregunta era específicamente sobre la propagación del estado de salida, puede estudiar el comportamiento predeterminado de esta manera:

% false | true
% echo $?
0

% true | false
% echo $?
1

Como puede ver, el estado de salida de una tubería es el estado de salida de su último comando.

alexis
fuente