Warum bleibt `echo | xargs > >(cat)` auf meinem Mac hängen?

Warum bleibt `echo | xargs > >(cat)` auf meinem Mac hängen?

Dies ist in zshund reproduzierbar bash.

Was mich noch mehr verwirrt: echo | ( xargs; : ) > >(cat)es hängt nicht. Dies ist auch in zshund reproduzierbar bash.

Wenn ich GNUs xargswie bereitgestellt verwende brew install findutils, bleibt es nicht hängen: echo | gxargs > >(cat).

Tatsächlich habe ich außer dem meines Systems kein anderes Programm gefunden xargs, das sich so verhält. Ich dachte, es könnte etwas xargsmit den Dateideskriptoren nicht stimmen, also habe ich versucht, es xargsdurch bash -c 'kill -9 $$'oder bash -c 'exec 0<&- 1<&-'oder viele andere Versuche zu ersetzen.

Ich habe auch auf ##mac, #macosx, ##linux, und #bashauf Freenode nach Hilfe gesucht, aber dort schien niemand zu wissen, was los war.Ich habe auch bei Stack Overflow gefragtaber es war nicht ganz programmierend genug.


> 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

Antwort1

Ich konnte den Quellcode für mein System finden, xargsindem strings $(which xargs)ich nach interessanten Schlüsselwörtern suchte. PROJECT:shell_cmds-207.40.1blieb mir auf und bald fand ich den Quellcode für eine etwas ältere Version shell_cmds-203aufApples Open Source-Site.

Ich habe die Version von xargsin diesem Paket mit kompiliert gcc -g *.c, ausgeführt echo | ./a.out > >(cat)und meinen Debugger lldban den a.outProzess angehängt. Ich fand heraus, dass es bei einem Aufruf von waitpidvon hängen blieb xargs.c:610(Quelle). Auszug:

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

Da xargses sich um ein kompliziertes Programm handelt, wollte ich ein kleineres C-Programm erstellen, das das Verhalten reproduzieren würde. Hier ist es:

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

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

Das Kompilieren mit gcc tiny.c -o tinyund Ausführen echo | ./tiny > >(cat)blieb genauso hängen wie xargs. Tatsächlich könnte ich es jetzt noch weiter vereinfachen, ./tiny > >(cat)würde hängen, während ( ./tiny; : ) > >(cat)nicht hängen würde.

Abgesehen davon: Dieses kleine Programm kann unter Linux kompiliert werden und dann können Sie dieses Verhalten unter Linux problemlos reproduzieren.

-1Wenn Sie an übergeben, waitpidwartet es aufbeliebiger untergeordneter Prozess. Das wirft die Frage auf:warum ist tinyein untergeordneter Prozess vorhanden, ./tiny > >(cat)aber nicht ( ./tiny; : ) > >(cat)?

Ich habe mich zwar nicht eingehend mit dem Quellcode befasst bash, habe aber eine ziemlich fundierte Vermutung darüber, was hier vor sich geht.

Lassen Sie uns zunächst den ersten Befehl analysieren: ./tiny > >(cat). Erstellt zuerst basheine benannte Pipe und fork()-exec()führt dann catdie Erstellung als untergeordneten Prozess durch. Dann legt es seine eigene stdoutals dieselbe benannte Pipe fest. bashBeendet schließlich seinen Lebenszyklus mit dem Aufruf exec()zur Umwandlung in tiny. tinyHat jetzt dieselbe PID und das Betriebssystem betrachtet den catProzess immer noch als sein untergeordnetes Element.

Wichtig ist, dass dasselbe mit passiert ( ./tiny ) > >(cat), aber es wird einfach exec()mit s in bash (Klammern starten eine Subshell) und dann in tiny. Ein wichtiger Punkt scheint zu sein, dass, wenn bashmit nur einem auszuführenden Befehl gestartet wird, dies nicht geschieht, fork()-exec()sondern einfach exec()sofort mit s ausgeführt wird.

Jetzt analysieren wir den zweiten Befehl: ( ./tiny; : ) > >(cat). Wir erhalten am Anfang dasselbe Ergebnis: fork()-exec()ing wird catins Leben gerufen. Dann bash exec()s wird in eine neue bashInstanz gerufen. Dann sieht es, dass es zwei auszuführende Befehle hat, also wird fork()-exec()s tinyins Leben gerufen, und da es sich aufgespalten hat, hat dieser neue tinyProzess nicht catals Kind, also bleibt er nicht hängen. Dann bashwird ausgeführt :( :ist ein spezielles integriertes, also gibt es hier kein exec, aber die Verwendung eines nicht integrierten würde trotzdem zu einer tinyAufspaltung führen, also würde es trotzdem nicht hängen bleiben).

verwandte Informationen