Esto es lo que considero que es la mejor solución de Bourne-Shell puro para ser usado como la base sobre la que se podía construir su "flota":
# You want to pipe command1 through command2:
exec 4>&1
exitstatus=`{ { command1; printf $? 1>&3; } | command2 1>&4; } 3>&1`
# $exitstatus now has command1's exit status.
Creo que esto se explica mejor desde adentro hacia afuera - comando1 ejecutará e imprimir su producción regular en la salida estándar (descriptor de archivo 1), a continuación, una vez que se hace, printf ejecutará y el código de salida de comando1 impresión en su salida estándar , pero ese stdout se redirige al descriptor de archivo 3.
Mientras se ejecuta el comando1, su salida estándar se canaliza a command2 (la salida de printf nunca llega a command2 porque la enviamos al descriptor de archivo 3 en lugar de 1, que es lo que lee el conducto). Luego, redirigimos la salida de command2 al descriptor de archivo 4, para que también permanezca fuera del descriptor de archivo 1 - porque queremos el descriptor 1 de archivo gratis un poco más tarde, porque llevaremos la salida printf en el descriptor 3 de archivo al descriptor de archivo 1 - porque eso es lo que capturará el comando (los backticks), y eso es lo que se colocará en la variable.
El último truco de la magia es que el primer exec 4>&1
lo hicimos como un comando separado: abre el descriptor de archivo 4 como una copia de la salida estándar del shell externo. La sustitución de comandos capturará lo que está escrito en estándar desde la perspectiva de los comandos que contiene, pero dado que la salida de command2 va a presentar el descriptor 4 en lo que respecta a la sustitución del comando, la sustitución del comando no lo captura, sin embargo, una vez que se "sale" de la sustitución del comando, de hecho sigue yendo al descriptor de archivo general del script 1.
(El tiene que ser un comando separado porque a muchos shells comunes no les gusta cuando intenta escriba en un descriptor de archivo dentro de una sustitución de comando, que se abre en el comando "externo" que está utilizando la sustitución. Por lo tanto, esta es la manera más simple de hacerlo).
Puede verlo de una forma menos técnica y más playf ul way, como si las salidas de los comandos se saltaran entre sí: command1 pipes a command2, luego la salida de printf salta sobre el comando 2 para que command2 no la atrape, y luego la salida del comando 2 salta y sale de la sustitución de comando justo cuando printf aterriza justo a tiempo para ser capturado por la sustitución para que termine en la variable, y la salida de command2 se transmite de manera feliz a la salida estándar, como en una tubería normal.
Además, según tengo entendido, $?
aún contendrá el código de retorno del segundo comando en la tubería, porque las asignaciones de variables, sustituciones de comandos y comandos compuestos son efectivamente transparentes para el código de retorno del comando dentro de ellos, por lo que el estado de retorno de command2 debería propagarse.
Una advertencia es que es posible que command1 en algún momento termine usando los descriptores de archivos 3 o 4, o que command2 o cualquiera de los comandos posteriores utilizará el descriptor de archivo 4, por lo que para ser más robusto, haría:
exec 4>&1
exitstatus=`{ { command1 3>&-; printf $? 1>&3; } 4>&- | command2 1>&4; } 3>&1`
exec 4>&-
Nota que utilizo comandos compuestos en mi ejemplo, pero subniveles (utilizando ()
en lugar de { }
también funcionará, aunque tal vez puede ser menos eficiente.)
comandos heredan descriptores de archivo del proceso que se inicia ellos, entonces toda la segunda línea heredará el descriptor de archivo cuatro, y el comando compuesto seguido de 3>&1
heredará el descriptor de archivo tres.Entonces, el 4>&-
se asegura de que el comando compuesto interno no herede el descriptor de archivo cuatro, y el 3>&-
no heredará el descriptor de archivo tres, por lo que command1 obtiene un entorno más limpio y más estándar. También puede mover el 4>&-
interno junto al 3>&-
, pero me imagino por qué no solo limita su alcance tanto como sea posible.
No estoy seguro de la frecuencia con que las cosas usan el descriptor de archivo tres y cuatro directamente - Creo que la mayoría de los programas usan syscalls que devuelven descriptores de archivos no utilizados en el momento, pero a veces escriben códigos en el descriptor de archivo 3 directamente, supongo (podría imaginarme un programa que verifica un descriptor de archivo para ver si está abierto, y si lo está usando, o si se comporta de manera diferente si no lo está). Por lo tanto, es probable que lo último sea mejor tenerlo en cuenta y usarlo para casos de propósito general.
--- contenido obsoleto DEBAJO DE ESTA LÍNEA ---
Por razones históricas, aquí es mi original, no-portátil-a-toda-conchas responden:
[EDIT] Mi mal, esta no funciona con bash porque bash necesita mímica extra cuando juega con los descriptores de archivos, lo actualizaré tan pronto como pueda. [/ EDIT]
puro solución shell Bourne:
exitstatus=`{ 3>&- command1; } 1>&3; printf $?` 3>&1 | command2
# $exitstatus now has command1's exit status.
Ésta es la base sobre la que se podía construir su "flota". Bofetada en un argumento de línea de comandos de análisis y todo lo que, a su vez comando2 en "T" con las opciones pertinentes, etc.
La explicación muy detallada es la siguiente:
En el nivel superior, la declaración es sólo una tubería entre dos comandos:
commandA | command2
Commanda a su vez se descompone en un solo comando con una reorientación de descriptor de archivo 3 al descriptor de archivo 1 (salida estándar):
commandB 3>&1
Esto significa que el shell esperará que el comando B escriba algo en el descriptor de archivo 3 - si el descriptor de archivo 3 nunca se abre, sería un error. También significa que va a conseguir lo comando2 commandB salidas en ambos descriptores de archivos 1 (stdout) y 3.
commandB a su vez es una asignación de variables con el comando substitución:
VAR_FOO=`commandC`
Sabemos que las asignaciones de variables no lo hacen imprima cualquier cosa en cualquier descriptor de archivo (y la salida estándar del comandoC se captura para la sustitución), así que sabemos que el comandoB como un todo no generará nada en stdout. comando2 será por lo tanto sólo ver lo que commandC escribe en el descriptor de archivo 3.
Y es commandC dos comandos, donde el segundo comando imprime el estado de salida de la primera:
commandD ; printf $?
Así que por ahora sabemos la asignación de variables en el último paso contendrá el estado de salida de comandoD.
Ahora, commandD descompone a otra redirección básica, de la salida estándar de un comman para presentar el descriptor 3:
commandE 1>&3
Así que ahora sabemos que la escritura que debe presentar el descriptor 3, y por lo tanto en última instancia a command2, es commandé de stdout.
Por último: commandé es un "comando compuesto" (también se puede utilizar un subnivel aquí, pero no es tan eficiente), envolviendo alrededor de otro tipo menos comúnmente visto de "redirección":
{ 3>&- command1; }
(Eso 3>&-
es un poco complicado, así que volveremos al final.) Así que los comandos compuestos hacen que ese punto y coma sea obligatorio cuando el último comando y el último par están en la misma línea, por eso está ahí. Así que sabemos que los comandos compuestos devuelven el código de salida de su último comando y heredan descriptores de archivos como todo lo demás, por lo que ahora sabemos que el comando stdout fluye fuera del comando compuesto, redirige al descriptor de archivo 3 para evitar ser atrapado por la sustitución del comando , mientras tanto, la sustitución de comando captura el stdout restante de printf, que hace eco del estado de salida de command1 una vez que finaliza.
Y ahora para el truco: 3>&-
dice "close file descriptor 3". Puedes pensar, "¿por qué lo cierras cuando redirigiste la salida de command1 a él?" Bueno, si miras detenidamente, verás que los efectos cercanos solo ordenan1 dentro del comando compuesto (dentro de las llaves) específicamente, mientras que la redirección afecta todo el comando compuesto.
Así que esto es lo que sucede: cuando los comandos individuales del comando compuesto se ejecutan, el shell abre el descriptor de archivo 3. Los procesos heredan descriptores de archivos, por lo que command1 se ejecuta de forma predeterminada con el descriptor de archivo abierto y 3 el mismo lugar también Esto es malo porque ocasionalmente, los programas realmente esperan que las descripciones de archivos específicos signifiquen cosas especiales: pueden comportarse de manera diferente cuando se abren con el descriptor de archivo 3 abierto. La solución más robusta es simplemente cerrar el descriptor de archivo 3 (o el número que use) solo para command1, de modo que se ejecute como lo haría si el descriptor de archivo 3 nunca se abriera.
Supongo que la verdadera pregunta aquí es: cómo dar salida T y capturar el estado de salida. Si es así: posible duplicado de [bash: salida T Y estado de salida de captura] (http://stackoverflow.com/questions/1221833/bash-tee-output-and-capture-exit-status) – lesmana