Esto es reproducible en zsh
y bash
.
Confundiéndome aún más, echo | ( xargs; : ) > >(cat)
no se cuelga. Esto también es reproducible en zsh
y bash
.
Si uso GNU xargs
según lo previsto, brew install findutils
no se bloquea: echo | gxargs > >(cat)
.
De hecho, no he encontrado ningún otro programa además del de mi sistema xargs
que se comporte de esta manera. Pensé que podría haber algo xargs
relacionado con los descriptores de archivos, así que intenté reemplazarlos xargs
con bash -c 'kill -9 $$'
muchas bash -c 'exec 0<&- 1<&-'
otras tomas en la oscuridad.
También busqué ayuda en ##mac
, #macosx
, ##linux
y #bash
en Freenode, pero nadie parecía saber lo que estaba pasando.También pregunté en Stack Overflowpero no fue suficiente programación.
> sw_vers | head -n 2
ProductName: Mac OS X
ProductVersion: 10.15.2
> zsh --version
zsh 5.7.1 (x86_64-apple-darwin19.0)
> bash --version | head -n 1
GNU bash, version 3.2.57(1)-release (x86_64-apple-darwin19)
> strings $(which xargs) | grep 'xargs.c'
$FreeBSD: src/usr.bin/xargs/xargs.c,v 1.57 2005/02/27 02:01:31 gad Exp $
> gxargs --version | head -n 1
xargs (GNU findutils) 4.7.0
Respuesta1
Pude encontrar el código fuente de mi sistema xargs
ejecutando strings $(which xargs)
y buscando palabras clave interesantes. PROJECT:shell_cmds-207.40.1
Me llamó la atención y pronto encontré el código fuente de una versión ligeramente anterior shell_cmds-203
enSitio de código abierto de Apple.
Compilé la versión de xargs
ese paquete con gcc -g *.c
, ejecuté echo | ./a.out > >(cat)
y adjunté mi depurador lldb
al a.out
proceso. Descubrí que estaba atascado en una llamada waitpid
desde xargs.c:610
(fuente). Extracto:
while ((pid = waitpid(-1, &status, !waitall && curprocs < maxprocs ?
WNOHANG : 0)) > 0) {
Como xargs
es un programa complicado, quería crear un programa C más pequeño que reprodujera el comportamiento. Aquí lo tienes:
// tiny.c
#include <sys/wait.h>
int main() {
int status;
waitpid(-1, &status, 0);
return 0;
}
Compilando eso gcc tiny.c -o tiny
y ejecutándolo, echo | ./tiny > >(cat)
se colgó como xargs
. De hecho, ahora podría simplificar aún más, ./tiny > >(cat)
colgaría y ( ./tiny; : ) > >(cat)
no colgaría.
Aparte: este pequeño programa se puede compilar en Linux y luego puedes reproducir este comportamiento en Linux fácilmente.
Pasar -1
a waitpid
hará que esperecualquier proceso hijo. Entonces eso plantea la pregunta:¿Por qué tiny
tiene un proceso hijo ./tiny > >(cat)
pero no ( ./tiny; : ) > >(cat)
?
No me he sumergido en bash
el código fuente de , pero tengo una idea bastante fundamentada de lo que está pasando.
Primero analicemos el primer comando: ./tiny > >(cat)
. Primero bash
crea una canalización con nombre y luego fork()-exec()
la cat
crea como un proceso secundario. Luego configura la suya propia stdout
para que sea la tubería con el mismo nombre. Finalmente bash
termina su vida llamando exec()
a transformarse en tiny
. Ahora tiny
tiene el mismo PID y el sistema operativo todavía considera que el cat
proceso es su hijo.
Es importante destacar que sucede lo mismo con ( ./tiny ) > >(cat)
pero simplemente exec()
está en bash (el paréntesis inicia un subshell) y luego en tiny
. Un hecho clave parece ser que cuando bash
se inicia con un solo comando para ejecutar, no lo hace, fork()-exec()
sino que simplemente exec()
se ejecuta de inmediato.
Ahora analicemos el segundo comando: ( ./tiny; : ) > >(cat)
. Al principio obtenemos lo mismo: fork()-exec()
nacer cat
. Luego bash
exec()
ingresa a una nueva bash
instancia. Luego ve que tiene dos comandos para ejecutar, por lo que fork()-exec()
existe tiny
y, debido a que se bifurcó, este nuevo tiny
proceso no tiene cat
como hijo, por lo que no se bloquea. Luego bash
se ejecuta :
( :
es un integrado especial, por lo que no hay ningún ejecutivo aquí, pero el uso de uno no integrado aún provocaría tiny
que se bifurcara, por lo que aún no se colgaría).