El
echo one; echo two > >(cat); echo three;
el comando da un resultado inesperado.
Leí esto:¿Cómo se implementa la sustitución de procesos en bash? y muchos otros artículos sobre la sustitución de procesos en Internet, pero no entiendo por qué se comporta de esta manera.
Resultado esperado:
one
two
three
Salida real:
prompt$ echo one; echo two > >(cat); echo three;
one
three
prompt$ two
Además, estos dos comandos deberían ser equivalentes desde mi punto de vista, pero no lo son:
##### first command - the pipe is used.
prompt$ seq 1 5 | cat
1
2
3
4
5
##### second command - the process substitution and redirection are used.
prompt$ seq 1 5 > >(cat)
prompt$ 1
2
3
4
5
¿Por qué creo que deberían ser iguales? Porque ambos conectan el seq
salida al cat
entrada a través de la canalización anónima:Wikipedia, sustitución de procesos.
Pregunta: ¿Por qué se comporta de esta manera? ¿Dónde está mi error? Se desea una respuesta completa (con una explicación de cómo bash
lo hace bajo el capó).
Respuesta aceptada:
Sí, en bash
como en ksh
(de donde proviene la función), los procesos dentro de la sustitución del proceso no se esperan (antes de ejecutar el siguiente comando en el script).
para un <(...)
one, eso suele estar bien como en:
cmd1 <(cmd2)
el shell estará esperando cmd1
y cmd1
normalmente estará esperando cmd2
en virtud de que lee hasta el final del archivo en la tubería que se sustituye, y ese final del archivo generalmente ocurre cuando cmd2
muere Esa es la misma razón por la que varios shells (no bash
) no se moleste en esperar cmd2
en cmd2 | cmd1
.
Para cmd1 >(cmd2)
, sin embargo, generalmente ese no es el caso, ya que es más cmd2
que normalmente espera cmd1
allí, por lo general, saldrá después.
Eso está arreglado en zsh
que espera cmd2
allí (pero no si lo escribes como cmd1 > >(cmd2)
y cmd1
no está integrado, use {cmd1} > >(cmd2)
en su lugar como está documentado).
ksh
no espera por defecto, pero le permite esperarlo con wait
incorporado (también hace que el pid esté disponible en $!
, aunque eso no ayuda si haces cmd1 >(cmd2) >(cmd3)
)
rc
(con el cmd1 >{cmd2}
sintaxis), igual que ksh
excepto que puede obtener los pids de todos los procesos en segundo plano con $apids
.
es
(también con cmd1 >{cmd2}
) espera cmd2
como en zsh
, y también espera cmd2
en <{cmd2}
redirecciones de procesos.
bash
hace el pid de cmd2
(o más exactamente de la subcapa, ya que ejecuta cmd2
en un proceso secundario de esa subcapa aunque sea el último comando allí) disponible en $!
, pero no te deja esperar.
Si tiene que usar bash
, puede solucionar el problema usando un comando que esperará ambos comandos con:
{ { cmd1 >(cmd2); } 3>&1 >&4 4>&- | cat; } 4>&1
Eso hace que ambos cmd1
y cmd2
tienen su fd 3 abierto a una tubería. cat
esperará el final del archivo en el otro extremo, por lo que normalmente solo saldrá cuando cmd1
y cmd2
estan muertos. Y el caparazón esperará a ese cat
dominio. Podrías verlo como una red para capturar la terminación de todos los procesos en segundo plano (puedes usarlo para otras cosas iniciadas en segundo plano como con &
, coprocs o incluso comandos que se ejecutan en segundo plano siempre que no cierren todos los descriptores de archivo como suelen hacer los demonios).
Tenga en cuenta que gracias al proceso de subcapa desperdiciado mencionado anteriormente, funciona incluso si cmd2
cierra su fd 3 (los comandos generalmente no hacen eso, pero algunos como sudo
o ssh
hacer). Futuras versiones de bash
eventualmente puede hacer la optimización allí como en otros shells. Entonces necesitarías algo como:
{ { cmd1 >(sudo cmd2; exit); } 3>&1 >&4 4>&- | cat; } 4>&1
Para asegurarse de que todavía hay un proceso de shell adicional con ese fd 3 abierto esperando ese sudo
comando.
Tenga en cuenta que cat
no leerá nada (ya que los procesos no escriben en su fd 3). Solo está ahí para la sincronización. Solo hará una read()
llamada al sistema que regresará sin nada al final.
De hecho, puedes evitar ejecutar cat
usando una sustitución de comando para hacer la sincronización de tubería:
{ unused=$( { cmd1 >(cmd2); } 3>&1 >&4 4>&-); } 4>&1
Esta vez, es el shell en lugar de cat
que está leyendo desde la tubería cuyo otro extremo está abierto en fd 3 de cmd1
y cmd2
. Estamos usando una asignación de variable, por lo que el estado de salida de cmd1
está disponible en $?
.
O podría hacer la sustitución del proceso a mano, y luego podría incluso usar el sh
de su sistema ya que eso se convertiría en la sintaxis estándar de shell:
{ cmd1 /dev/fd/3 3>&1 >&4 4>&- | cmd2 4>&-; } 4>&1
aunque tenga en cuenta, como se señaló anteriormente, que no todos los sh
las implementaciones esperarían cmd1
después de cmd2
ha terminado (aunque eso es mejor que al revés). Esa vez, $?
contiene el estado de salida de cmd2
; aunque bash
y zsh
hacer cmd1
Estado de salida disponible en ${PIPESTATUS[0]}
y $pipestatus[1]
respectivamente (ver también el pipefail
opción en algunas conchas así que $?
puede informar la falla de los componentes de la tubería que no sean el último)
Tenga en cuenta que yash
tiene problemas similares con su proceso de redireccionamiento rasgo. cmd1 >(cmd2)
se escribiría cmd1 /dev/fd/3 3>(cmd2)
allí. Pero cmd2
no se espera y no puede usar wait
esperarlo tampoco y su pid no está disponible en el $!
variable tampoco. Usaría las mismas soluciones alternativas que para bash
.