Así que quería aportar una respuesta como la de lesmana, pero creo que la mía es quizás una solución un poco más simple y un poco más ventajosa de Bourne-shell:
# 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 de adentro hacia afuera: command1 se ejecutará e imprimirá su salida regular en stdout (descriptor de archivo 1), luego, una vez hecho, printf se ejecutará e imprimirá el código de salida de icommand1 en su stdout, pero esa stdout se redirige a descriptor de archivo 3.
Mientras se ejecuta command1, su stdout se canaliza a command2 (la salida de printf nunca llega a command2 porque lo enviamos al descriptor de archivo 3 en lugar de 1, que es lo que lee la tubería). Luego, redirigimos la salida del comando 2 al descriptor de archivo 4, de modo que también quede fuera del descriptor de archivo 1, porque queremos que el descriptor de archivo 1 esté libre un poco más tarde, porque traeremos la salida de printf en el descriptor de archivo 3 nuevamente al descriptor de archivo 1 - porque eso es lo que capturará la sustitución del comando (los backticks), y eso es lo que se colocará en la variable.
La última parte de la magia es que primero exec 4>&1
lo hicimos como un comando separado: abre el descriptor de archivo 4 como una copia del stdout del shell externo. La sustitución de comandos capturará todo lo que está escrito en el estándar desde la perspectiva de los comandos dentro de él, pero dado que la salida del comando2 va al descriptor de archivo 4 en lo que respecta a la sustitución de comandos, la sustitución de comandos no lo captura, sin embargo, una vez que "sale" de la sustitución del comando, efectivamente sigue yendo al descriptor de archivo general del script 1.
( exec 4>&1
Tiene que ser un comando separado porque a muchos shells comunes no les gusta cuando intentas escribir en un descriptor de archivo dentro de una sustitución de comando, que se abre en el comando "externo" que está usando la sustitución. Así que este es el La forma portátil más sencilla de hacerlo).
Puede verlo de una manera menos técnica y más lúdica, como si las salidas de los comandos se saltaran entre sí: command1 se canaliza hacia command2, luego la salida de printf salta sobre el comando 2 para que command2 no lo atrape, y luego La salida del comando 2 salta y sale de la sustitución del comando justo cuando printf aterriza justo a tiempo para ser capturado por la sustitución de modo que termine en la variable, y la salida del comando 2 se escribe alegremente en la salida estándar, tal como en una tubería normal
Además, según tengo entendido, $?
seguirá conteniendo el código de retorno del segundo comando en la tubería, porque las asignaciones de variables, las sustituciones de comandos y los comandos compuestos son efectivamente transparentes al código de retorno del comando dentro de ellos, por lo que el estado de retorno de command2 debería propagarse; esto, y no tener que definir una función adicional, es la razón por la que creo que esta podría ser una solución algo mejor que la propuesta por lesmana.
Según las advertencias que menciona lesmana, es posible que command1 en algún momento termine usando los descriptores de archivo 3 o 4, por lo que para ser más robusto, haría lo siguiente:
exec 4>&1
exitstatus=`{ { command1 3>&-; printf $? 1>&3; } 4>&- | command2 1>&4; } 3>&1`
exec 4>&-
Tenga en cuenta que utilizo comandos compuestos en mi ejemplo, pero subcapas (usar en ( )
lugar de { }
también funcionará, aunque tal vez sea menos eficiente).
Los comandos heredan los descriptores de archivo del proceso que los inicia, por lo que toda la segunda línea heredará el descriptor de archivo cuatro, y el comando compuesto seguido 3>&1
heredará el descriptor de archivo tres. Por lo tanto, 4>&-
se asegura de que el comando compuesto interno no heredará el descriptor de archivo cuatro, y 3>&-
no heredará el descriptor de archivo tres, por lo que command1 obtiene un entorno más limpio y estándar. También puede mover el interior al 4>&-
lado del 3>&-
, pero me imagino por qué no limitar su alcance tanto como sea posible.
No estoy seguro de con qué frecuencia las cosas usan el descriptor de archivo tres y cuatro directamente; creo que la mayoría de las veces los programas usan syscalls que devuelven descriptores de archivo no utilizados en este momento, pero a veces las escrituras de código en el descriptor de archivo 3 directamente, yo Supongo (podría imaginar un programa revisando un descriptor de archivo para ver si está abierto, y usándolo si lo está, o comportándose de manera diferente si no lo está). Por lo tanto, lo último es probablemente mejor tener en cuenta y usar para casos de uso general.