Kann jemand ein paar Beispiele zur Verwendung von coproc
geben ?
Akzeptierte Antwort:
Co-Prozesse sind ein ksh
Funktion (bereits in ksh88
). zsh
hatte das Feature von Anfang an (Anfang der 90er), während es gerade erst zu bash
hinzugefügt wurde in 4.0
(2009).
Das Verhalten und die Benutzeroberfläche unterscheiden sich jedoch erheblich zwischen den 3 Shells.
Die Idee ist jedoch dieselbe:Es ermöglicht, einen Job im Hintergrund zu starten und ihm Eingaben zu senden und seine Ausgabe zu lesen, ohne auf Named Pipes zurückgreifen zu müssen.
Das wird mit unbenannten Pipes mit den meisten Shells und Socketpairs mit neueren Versionen von ksh93 auf einigen Systemen gemacht.
In a | cmd | b
, a
speist Daten an cmd
und b
liest seine Ausgabe. Ausführen von cmd
als Co-Prozess erlaubt es der Shell, sowohl a
zu sein und b
.
ksh co-prozessiert
In ksh
, starten Sie einen Coprozess als:
cmd |&
Sie füttern Daten zu cmd
indem Sie Dinge tun wie:
echo test >&p
oder
print -p test
Und lesen Sie cmd
’s-Ausgabe mit Dingen wie:
read var <&p
oder
read -p var
cmd
wie jeder Hintergrundjob gestartet wird, können Sie fg
verwenden , bg
, kill
darauf und verweise darauf mit %job-number
oder über $!
.
Zum Schließen des Schreibendes der Pipe cmd
liest, können Sie Folgendes tun:
exec 3>&p 3>&-
Und um das Leseende der anderen Pipe zu schließen (die eine cmd
schreibt an):
exec 3<&p 3<&-
Sie können keinen zweiten Co-Prozess starten, es sei denn, Sie speichern zuerst die Pipe-Dateideskriptoren in einigen anderen fds. Zum Beispiel:
tr a b |&
exec 3>&p 4<&p
tr b c |&
echo aaa >&3
echo bbb >&p
zsh-Co-Prozesse
In zsh
, Co-Prozesse sind nahezu identisch mit denen in ksh
. Der einzige wirkliche Unterschied ist, dass zsh
Co-Prozesse werden mit dem coproc
gestartet Schlüsselwort.
coproc cmd
echo test >&p
read var <&p
print -p test
read -p var
Tun:
exec 3>&p
Hinweis:Dadurch wird coproc
nicht verschoben Dateideskriptor zu fd 3
(wie in ksh
), sondern dupliziert es. Es gibt also keine explizite Möglichkeit, die Zuführ- oder Leseleitung zu schließen, sondern eine andere zu starten coproc
.
Zum Beispiel, um das Einzugsende zu schließen:
coproc tr a b
echo aaaa >&p # send some data
exec 4<&p # preserve the reading end on fd 4
coproc : # start a new short-lived coproc (runs the null command)
cat <&4 # read the output of the first coproc
Zusätzlich zu Pipe-basierten Co-Prozessen, zsh
(seit 3.1.6-dev19, veröffentlicht im Jahr 2000) hat Pseudo-tty-basierte Konstrukte wie expect
. Um mit den meisten Programmen zu interagieren, funktionieren Co-Prozesse im ksh-Stil nicht, da Programme mit dem Puffern beginnen, wenn ihre Ausgabe eine Pipe ist.
Hier sind einige Beispiele.
Starten Sie den Co-Prozess x
:
zmodload zsh/zpty
zpty x cmd
(Hier cmd
ist ein einfacher Befehl. Aber mit eval
können Sie schickere Sachen machen oder Funktionen.)
Feed a co-process data:
zpty -w x some data
Mitprozessdaten lesen (im einfachsten Fall):
zpty -r x var
Wie expect
, kann es auf eine Ausgabe des Co-Prozesses warten, die einem bestimmten Muster entspricht.
bash-Co-Prozesse
Die Bash-Syntax ist viel neuer und baut auf einer neuen Funktion auf, die kürzlich zu ksh93, bash und zsh hinzugefügt wurde. Es stellt eine Syntax bereit, um die Handhabung von dynamisch zugewiesenen Dateideskriptoren über 10 zu ermöglichen.
bash
bietet eine Grundlage coproc
Syntax und eine erweiterte eins.
Grundlegende Syntax
Die grundlegende Syntax zum Starten eines Co-Prozesses sieht wie folgt aus:zsh
s:
coproc cmd
In ksh
oder zsh
, auf die Pipes zum und vom Co-Prozess wird mit >&p
zugegriffen und <&p
.
Aber in bash
, werden die Dateideskriptoren der Pipe vom Co-Prozess und der anderen Pipe zum Co-Prozess in $COPROC
zurückgegeben Array (bzw. ${COPROC[0]}
und ${COPROC[1]}
. Also…
Füttern Sie Daten an den Co-Prozess:
echo xxx >&"${COPROC[1]}"
Daten aus dem Co-Prozess lesen:
read var <&"${COPROC[0]}"
Mit der grundlegenden Syntax können Sie jeweils nur einen Co-Prozess starten.
Erweiterte Syntax
In der erweiterten Syntax können Sie benennen Ihre Co-Prozesse (wie in zsh
zpty-Co-Prozesse):
coproc mycoproc { cmd; }
Der Befehl hat ein zusammengesetzter Befehl sein. (Beachten Sie, dass das obige Beispiel an function f { ...; }
erinnert .)
Diesmal befinden sich die Dateideskriptoren in ${mycoproc[0]}
und ${mycoproc[1]}
.
Sie können mehr als einen Co-Prozess gleichzeitig starten – aber Sie tun eine Warnung erhalten, wenn Sie einen Co-Prozess starten, während einer noch läuft (sogar im nicht-interaktiven Modus).
Sie können die Dateideskriptoren schließen, wenn Sie die erweiterte Syntax verwenden.
coproc tr { tr a b; }
echo aaa >&"${tr[1]}"
exec {tr[1]}>&-
cat <&"${tr[0]}"
Beachten Sie, dass das Schließen auf diese Weise in Bash-Versionen vor 4.3 nicht funktioniert, wo Sie es stattdessen schreiben müssen:
fd=${tr[1]}
exec {fd}>&-
Wie in ksh
und zsh
, sind diese Pipe-Dateideskriptoren als close-on-exec.
Aber in bash
, die einzige Möglichkeit, diese an ausgeführte Befehle zu übergeben, besteht darin, sie in fds zu duplizieren ,
1
, oder 2
. Dadurch wird die Anzahl der Co-Prozesse begrenzt, mit denen Sie für einen einzelnen Befehl interagieren können. (Ein Beispiel finden Sie weiter unten.)
Yash-Prozess und Pipeline-Umleitung
yash
hat per se kein Co-Process-Feature, aber das gleiche Konzept kann mit seiner Pipeline implementiert werden und verarbeiten Umleitungsfunktionen. yash
hat eine Schnittstelle zur pipe()
Systemaufruf, so etwas kann dort relativ einfach von Hand erledigt werden.
Sie würden einen Co-Prozess starten mit:
exec 5>>|4 3>(cmd >&5 4<&- 5>&-) 5>&-
Was zuerst eine pipe(4,5)
erzeugt (5 das schreibende Ende, 4 das lesende Ende), leitet dann fd 3 zu einer Pipe zu einem Prozess um, der mit seiner stdin am anderen Ende läuft, und stdout geht zu der zuvor erstellten Pipe. Dann schließen wir das schreibende Ende dieser Pipe im übergeordneten Element, das wir nicht benötigen. Jetzt haben wir also in der Shell fd 3 mit der Standardeingabe von cmd und fd 4 mit Pipes mit der Standardausgabe von cmd verbunden.
Beachten Sie, dass das close-on-exec-Flag bei diesen Dateideskriptoren nicht gesetzt ist.
So füttern Sie Daten:
echo data >&3 4<&-
So lesen Sie Daten:
read var <&4 3>&-
Und Sie können fds wie gewohnt schließen:
exec 3>&- 4<&-
Nun, warum sie nicht so beliebt sind
kaum Vorteile gegenüber Named Pipes
Co-Prozesse lassen sich einfach mit Standard Named Pipes implementieren. Ich weiß nicht, wann genau benannte Pipes eingeführt wurden, aber es ist möglich, dass es nach ksh
war kam mit Co-Prozessen auf (wahrscheinlich Mitte der 80er, ksh88 wurde 88 „veröffentlicht“, aber ich glaube ksh
wurde einige Jahre zuvor intern bei AT&T verwendet), was erklären würde, warum.
cmd |&
echo data >&p
read var <&p
Kann geschrieben werden mit:
mkfifo in out
cmd <in >out &
exec 3> in 4< out
echo data >&3
read var <&4
Die Interaktion mit diesen ist einfacher – insbesondere, wenn Sie mehr als einen Co-Prozess ausführen müssen. (Siehe Beispiele unten.)
Der einzige Vorteil der Verwendung von coproc
ist, dass Sie diese Named Pipes nach Gebrauch nicht aufräumen müssen.
Deadlock-anfällig
Shells verwenden Pipes in einigen Konstrukten:
- Muschelrohre:
cmd1 | cmd2
, - Befehlsersetzung:
$(cmd)
, - und Prozesssubstitution:
<(cmd)
,>(cmd)
.
In diesen fließen die Daten nur in einem ein Richtung zwischen verschiedenen Prozessen.
Bei Co-Prozessen und Named Pipes gerät man jedoch leicht in einen Deadlock. Sie müssen verfolgen, welcher Befehl welchen Dateideskriptor geöffnet hat, um zu verhindern, dass einer geöffnet bleibt und einen Prozess am Leben erhält. Deadlocks können schwierig zu untersuchen sein, da sie nicht deterministisch auftreten können; zum Beispiel nur, wenn so viele Daten gesendet werden, dass eine Pipe gefüllt wird.
funktioniert schlechter als expect
für das, wofür es entwickelt wurde
Der Hauptzweck von Co-Prozessen bestand darin, der Shell eine Möglichkeit zu bieten, mit Befehlen zu interagieren. Allerdings funktioniert es nicht so gut.
Die einfachste Form des oben erwähnten Deadlocks ist:
tr a b |&
echo a >&p
read var<&p
Da seine Ausgabe nicht an ein Terminal geht, tr
puffert seine Ausgabe. Es wird also nichts ausgeben, bis es entweder das Dateiende auf seiner stdin
sieht , oder es hat einen Puffer voller Daten für die Ausgabe angesammelt. Also oben, nachdem die Shell an
ausgegeben hat (nur 2 Bytes), den read
wird auf unbestimmte Zeit blockiert, weil tr
wartet darauf, dass die Shell weitere Daten sendet.
Kurz gesagt, Pipes sind nicht gut für die Interaktion mit Befehlen. Co-Prozesse können nur verwendet werden, um mit Befehlen zu interagieren, die ihre Ausgabe nicht puffern, oder Befehle, denen gesagt werden kann, dass sie ihre Ausgabe nicht puffern sollen; beispielsweise durch Verwendung von stdbuf
mit einigen Befehlen auf neueren GNU- oder FreeBSD-Systemen.
Deshalb expect
oder zpty
Verwenden Sie stattdessen Pseudo-Terminals. expect
ist ein Tool, das für die Interaktion mit Befehlen entwickelt wurde, und es macht es gut.
Die Handhabung von Dateideskriptoren ist fummelig und schwer richtig zu machen
Co-Prozesse können verwendet werden, um komplexere Installationen durchzuführen, als es einfache Shell-Rohre erlauben.
diese andere Unix.SE-Antwort hat ein Beispiel für eine Coproc-Nutzung.
Hier ist ein vereinfachtes Beispiel: Stellen Sie sich vor, Sie möchten eine Funktion, die eine Kopie der Ausgabe eines Befehls an 3 andere Befehle weiterleitet und dann die Ausgabe dieser 3 Befehle verkettet.
Alle mit Pipes.
Zum Beispiel:füttere die Ausgabe von printf '%sn' foo bar
zu tr a b
, sed 's/./&&/g'
, und cut -b2-
um so etwas zu erhalten:
foo
bbr
ffoooo
bbaarr
oo
ar
Erstens ist es nicht unbedingt offensichtlich, aber es besteht die Möglichkeit eines Deadlocks, und es wird nach nur wenigen Kilobyte Daten passieren.
Dann werden Sie, abhängig von Ihrer Shell, auf eine Reihe verschiedener Probleme stoßen, die unterschiedlich angegangen werden müssen.
Verwandte:Wie wird der Platzhalter * als Befehl interpretiert?
Zum Beispiel mit zsh
, würden Sie es tun mit:
f() (
coproc tr a b
exec {o1}<&p {i1}>&p
coproc sed 's/./&&/g' {i1}>&- {o1}<&-
exec {o2}<&p {i2}>&p
coproc cut -c2- {i1}>&- {o1}<&- {i2}>&- {o2}<&-
tee /dev/fd/$i1 /dev/fd/$i2 >&p {o1}<&- {o2}<&- &
exec cat /dev/fd/$o1 /dev/fd/$o2 - <&p {i1}>&- {i2}>&-
)
printf '%sn' foo bar | f
Oben haben die Co-Prozess-fds das Close-on-exec-Flag gesetzt, aber nicht diejenigen, die von ihnen dupliziert werden (wie in {o1}<&p
). Um Deadlocks zu vermeiden, müssen Sie also sicherstellen, dass sie in allen Prozessen geschlossen sind, die sie nicht benötigen.
Ebenso müssen wir eine Subshell verwenden und exec cat
verwenden am Ende, um sicherzustellen, dass es keinen Shell-Prozess gibt, der herumliegt, um ein Rohr offen zu halten.
Mit ksh
(hier ksh93
), das müsste lauten:
f() (
tr a b |&
exec {o1}<&p {i1}>&p
sed 's/./&&/g' |&
exec {o2}<&p {i2}>&p
cut -c2- |&
exec {o3}<&p {i3}>&p
eval 'tee "/dev/fd/$i1" "/dev/fd/$i2"' >&"$i3" {i1}>&"$i1" {i2}>&"$i2" &
eval 'exec cat "/dev/fd/$o1" "/dev/fd/$o2" -' <&"$o3" {o1}<&"$o1" {o2}<&"$o2"
)
printf '%sn' foo bar | f
(Hinweis: Das funktioniert nicht auf Systemen, auf denen ksh
verwendet socketpairs
statt pipes
, und wo /dev/fd/n
funktioniert wie unter Linux.)
In ksh
, fds über 2
sind mit dem Flag close-on-exec gekennzeichnet, es sei denn, sie werden explizit auf der Befehlszeile übergeben. Deshalb müssen wir die unbenutzten Dateideskriptoren nicht wie mit zsh
schließen – aber das ist auch der Grund, warum wir {i1}>&$i1
ausführen müssen und verwenden Sie eval
für diesen neuen Wert von $i1
, zu übergeben an tee
und cat
…
In bash
Dies ist nicht möglich, da Sie das Flag close-on-exec nicht vermeiden können.
Oben ist es relativ einfach, weil wir nur einfache externe Befehle verwenden. Es wird komplizierter, wenn Sie stattdessen Shell-Konstrukte verwenden möchten und Sie auf Shell-Fehler stoßen.
Vergleichen Sie das obige mit dem gleichen mit Named Pipes:
f() {
mkfifo p{i,o}{1,2,3}
tr a b < pi1 > po1 &
sed 's/./&&/g' < pi2 > po2 &
cut -c2- < pi3 > po3 &
tee pi{1,2} > pi3 &
cat po{1,2,3}
rm -f p{i,o}{1,2,3}
}
printf '%sn' foo bar | f
Schlussfolgerung
Wenn Sie mit einem Befehl interagieren möchten, verwenden Sie expect
, oder zsh
ist zpty
, oder Named Pipes.
Wenn Sie mit Rohren ausgefallene Klempnerarbeiten durchführen möchten, verwenden Sie Named Pipes.
Co-Prozesse können einige der oben genannten Aufgaben erfüllen, aber seien Sie bereit, sich für alles, was nicht trivial ist, ernsthaft den Kopf zu zerbrechen.