2 votes

Pourquoi `echo | xargs > >(cat)` se bloque-t-il sur mon Mac ?

Ceci est reproductible dans zsh y bash .

Ce qui m'embrouille encore plus, echo | ( xargs; : ) > >(cat) ne s'accroche pas. Ceci est également reproductible dans zsh y bash .

Si j'utilise le programme GNU xargs conformément aux dispositions de la brew install findutils il ne s'accroche pas : echo | gxargs > >(cat) .

En effet, je n'ai pas trouvé d'autre programme que celui de mon système. xargs qui se comporte de cette façon. Je me suis dit qu'il y avait peut-être quelque chose xargs fait avec les descripteurs de fichiers, j'ai donc essayé de remplacer xargs con bash -c 'kill -9 $$' o bash -c 'exec 0<&- 1<&-' ou de nombreux autres clichés dans le noir.

J'ai également cherché de l'aide sur ##mac , #macosx , ##linux y #bash sur Freenode mais personne ne semblait savoir ce qui se passait. J'ai également demandé sur Stack Overflow mais ce n'était pas assez pour la programmation.


> 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

3voto

johncs Points 51

J'ai pu trouver le code source de mon système. xargs en courant strings $(which xargs) et à la recherche de mots-clés intéressants. PROJECT:shell_cmds-207.40.1 m'a interpellé et j'ai rapidement trouvé le code source d'une version légèrement plus ancienne. shell_cmds-203 sur Le site Open Source d'Apple .

J'ai compilé la version de xargs dans ce paquet avec gcc -g *.c , a couru echo | ./a.out > >(cat) et j'ai attaché mon débogueur lldb à la a.out processus. J'ai découvert qu'il était coincé dans un appel à waitpid de xargs.c:610 ( source ). Extrait :

while ((pid = waitpid(-1, &status, !waitall && curprocs < maxprocs ?
        WNOHANG : 0)) > 0) {

Parce que xargs est un programme compliqué, je voulais faire un petit programme en C qui reproduirait le comportement. Le voici :

// tiny.c
#include <sys/wait.h>

int main() {
    int status;
    waitpid(-1, &status, 0);
    return 0;
}

En compilant cela avec gcc tiny.c -o tiny et en cours d'exécution echo | ./tiny > >(cat) accroché comme xargs . En effet, je pourrais maintenant simplifier encore plus, ./tiny > >(cat) seraient suspendus, tandis que ( ./tiny; : ) > >(cat) ne s'accrocherait pas.

En outre, ce petit programme peut être compilé sur Linux et vous pouvez alors reproduire ce comportement sur Linux facilement.

Passing -1 a waitpid le fera attendre sur tout processus enfant . Cela pose donc la question : pourquoi tiny ont un processus enfant dans ./tiny > >(cat) mais pas ( ./tiny; : ) > >(cat) ?

Je n'ai pas plongé dans bash Je ne connais pas le code source de l'entreprise, mais j'ai une idée assez précise de ce qui se passe.

D'abord, disséquons la première commande : ./tiny > >(cat) . Premier bash crée un tuyau nommé, et ensuite fork()-exec() s cat dans la création en tant que processus enfant. Ensuite, il définit ses propres stdout pour être ce même tuyau nommé. Enfin, bash termine sa vie en appelant exec() pour se transformer en tiny . Maintenant tiny a le même PID et le système d'exploitation considère toujours la cat pour être son enfant.

Il est important de noter que la même chose se produit avec ( ./tiny ) > >(cat) mais c'est juste exec() s dans bash (les parenthèses démarrent un sous-shell) et ensuite dans tiny . Un fait essentiel semble être que lorsque bash est démarré avec une seule commande à exécuter, il n'est pas fork()-exec() mais plutôt exec() immédiatement.

Maintenant, disséquons la deuxième commande : ( ./tiny; : ) > >(cat) . On a la même chose au début : fork()-exec() ing cat à l'existence. Puis bash exec() dans une nouvelle bash instance. Puis il voit qu'il a deux commandes à exécuter, donc il fork()-exec() s tiny à l'existence, et parce qu'elle a bifurqué, cette nouvelle tiny Le processus n'a pas cat comme un enfant pour qu'il ne soit pas suspendu. Puis bash exécute : ( : est un intégré spécial, donc il n'y a pas d'exécution ici, mais l'utilisation d'un non intégré causerait toujours tiny pour être bifurqué afin qu'il n'y ait toujours pas d'accrochage).

SistemesEz.com

SystemesEZ est une communauté de sysadmins où vous pouvez résoudre vos problèmes et vos doutes. Vous pouvez consulter les questions des autres sysadmins, poser vos propres questions ou résoudre celles des autres.

Powered by:

X