Dies ist in zsh
und reproduzierbar bash
.
Was mich noch mehr verwirrt: echo | ( xargs; : ) > >(cat)
es hängt nicht. Dies ist auch in zsh
und reproduzierbar bash
.
Wenn ich GNUs xargs
wie 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 xargs
mit den Dateideskriptoren nicht stimmen, also habe ich versucht, es xargs
durch bash -c 'kill -9 $$'
oder bash -c 'exec 0<&- 1<&-'
oder viele andere Versuche zu ersetzen.
Ich habe auch auf ##mac
, #macosx
, ##linux
, und #bash
auf 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, xargs
indem strings $(which xargs)
ich nach interessanten Schlüsselwörtern suchte. PROJECT:shell_cmds-207.40.1
blieb mir auf und bald fand ich den Quellcode für eine etwas ältere Version shell_cmds-203
aufApples Open Source-Site.
Ich habe die Version von xargs
in diesem Paket mit kompiliert gcc -g *.c
, ausgeführt echo | ./a.out > >(cat)
und meinen Debugger lldb
an den a.out
Prozess angehängt. Ich fand heraus, dass es bei einem Aufruf von waitpid
von hängen blieb xargs.c:610
(Quelle). Auszug:
while ((pid = waitpid(-1, &status, !waitall && curprocs < maxprocs ?
WNOHANG : 0)) > 0) {
Da xargs
es 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 tiny
und 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.
-1
Wenn Sie an übergeben, waitpid
wartet es aufbeliebiger untergeordneter Prozess. Das wirft die Frage auf:warum ist tiny
ein 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 bash
eine benannte Pipe und fork()-exec()
führt dann cat
die Erstellung als untergeordneten Prozess durch. Dann legt es seine eigene stdout
als dieselbe benannte Pipe fest. bash
Beendet schließlich seinen Lebenszyklus mit dem Aufruf exec()
zur Umwandlung in tiny
. tiny
Hat jetzt dieselbe PID und das Betriebssystem betrachtet den cat
Prozess 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 bash
mit 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 cat
ins Leben gerufen. Dann bash
exec()
s wird in eine neue bash
Instanz gerufen. Dann sieht es, dass es zwei auszuführende Befehle hat, also wird fork()-exec()
s tiny
ins Leben gerufen, und da es sich aufgespalten hat, hat dieser neue tiny
Prozess nicht cat
als Kind, also bleibt er nicht hängen. Dann bash
wird ausgeführt :
( :
ist ein spezielles integriertes, also gibt es hier kein exec, aber die Verwendung eines nicht integrierten würde trotzdem zu einer tiny
Aufspaltung führen, also würde es trotzdem nicht hängen bleiben).