Antworten:
Co-Prozesse sind ein ksh
Feature (bereits in ksh88
). zsh
hat das Feature von Anfang an (Anfang der 90er Jahre), während es erst bash
in 4.0
(2009) hinzugefügt wurde .
Das Verhalten und die Benutzeroberfläche unterscheiden sich jedoch erheblich zwischen den drei Schalen.
Die Idee ist jedoch dieselbe: Sie können einen Job im Hintergrund starten und ihn als Eingabe senden und auslesen, ohne auf Named Pipes zurückgreifen zu müssen.
Dies geschieht mit unbenannten Pipes mit den meisten Shells und Socket-Paaren mit den neuesten Versionen von ksh93 auf einigen Systemen.
In a | cmd | b
, a
speist Daten cmd
und b
liest seinen Ausgang. Das Ausführen cmd
als Co-Prozess ermöglicht, dass die Shell sowohl a
als auch ist b
.
In ksh
starten Sie einen Coprozess als:
cmd |&
Sie geben Daten ein, cmd
indem Sie Folgendes tun:
echo test >&p
oder
print -p test
Und lies cmd
's Ausgabe mit Dingen wie:
read var <&p
oder
read -p var
cmd
wie jeder Hintergrundjob gestartet wird, können Sie verwenden fg
, bg
, kill
auf sie und beziehen es von %job-number
oder über $!
.
Um das schreibende Ende der Pipe zu schließen, aus dem cmd
gelesen wird, können Sie Folgendes tun:
exec 3>&p 3>&-
Und um das Leseende der anderen Pipe zu schließen (in die geschrieben cmd
wird):
exec 3<&p 3<&-
Sie können einen zweiten Co-Prozess erst starten, wenn Sie die Pipe-Dateideskriptoren auf einem anderen FDS gespeichert haben. Zum Beispiel:
tr a b |&
exec 3>&p 4<&p
tr b c |&
echo aaa >&3
echo bbb >&p
In zsh
sind Co-Prozesse fast identisch mit denen in ksh
. Der einzige wirkliche Unterschied besteht darin, dass zsh
Co-Prozesse mit dem coproc
Schlüsselwort gestartet werden .
coproc cmd
echo test >&p
read var <&p
print -p test
read -p var
Tun:
exec 3>&p
Hinweis: Dadurch wird der coproc
Dateideskriptor nicht nach fd verschoben 3
(wie in ksh
), sondern dupliziert. Es gibt also keine explizite Möglichkeit, die Zufuhr- oder Leseleitung zu schließen, sondern eine andere coproc
.
So schließen Sie zum Beispiel das Einzugsende:
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
Neben Pipe-basierten Co-Prozessen zsh
(seit 3.1.6-dev19, veröffentlicht im Jahr 2000) gibt es auch Pseudotty-basierte Konstrukte wie expect
. Um mit den meisten Programmen zu interagieren, funktionieren Co-Prozesse im ksh-Stil nicht, da Programme mit der Pufferung 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 Sie können mit eval
oder Funktionen ausgefallenere Dinge tun .)
Feed ein Co-Prozess-Daten:
zpty -w x some data
Co-Process-Daten lesen (im einfachsten Fall):
zpty -r x var
So expect
kann es auf eine Ausgabe des Co-Prozesses warten, die mit einem bestimmten Muster übereinstimmt.
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 bietet eine Syntax, mit der dynamisch zugewiesene Dateideskriptoren über 10 verarbeitet werden können.
bash
Bietet eine grundlegende coproc
und eine erweiterte Syntax .
Die grundlegende Syntax zum Starten eines Co-Prozesses sieht wie zsh
folgt aus:
coproc cmd
In ksh
oder zsh
werden die Rohre zu und von dem Co-Prozess zugegriffen mit >&p
und <&p
.
In bash
werden jedoch die Dateideskriptoren der Pipe vom Co-Prozess und der anderen Pipe zum Co-Prozess im $COPROC
Array (bzw. ${COPROC[0]}
und) zurückgegeben ${COPROC[1]}
.
Daten in den Co-Prozess einspeisen:
echo xxx >&"${COPROC[1]}"
Daten aus dem Co-Prozess lesen:
read var <&"${COPROC[0]}"
Mit der Basissyntax können Sie jeweils nur einen Co-Prozess starten.
In der erweiterten Syntax können Sie nennen Ihre Co-Prozesse (wie in zsh
zpty Co-proccesses):
coproc mycoproc { cmd; }
Der Befehl muss ein zusammengesetzter Befehl sein. (Beachten Sie, wie das obige Beispiel erinnert function f { ...; }
.)
Diesmal befinden sich die Dateideskriptoren in ${mycoproc[0]}
und ${mycoproc[1]}
.
Sie können bei einer mehr als ein Co-Prozess Startzeit, aber Sie tun eine Warnung erhalten , wenn Sie einen Co-Prozess starten , während man noch (auch in nicht-interaktiven Modus) ausgeführt wird .
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, in denen Sie es stattdessen schreiben müssen:
fd=${tr[1]}
exec {fd}>&-
Wie in ksh
und zsh
werden diese Pipe-Dateideskriptoren als ausführbar markiert.
Aber bash
der einzige Weg , um diejenigen zu ausgeführten Befehle zu übergeben ist , sie zu fds duplizieren 0
, 1
oder 2
. Dies begrenzt die Anzahl der Co-Prozesse, mit denen Sie für einen einzelnen Befehl interagieren können. (Ein Beispiel finden Sie unten.)
yash
Es gibt an sich keine Co-Process-Funktion, aber dasselbe Konzept kann mit den Pipeline- und Prozessumleitungsfunktionen implementiert werden . yash
hat eine Schnittstelle zum pipe()
Systemaufruf, so dass dies dort relativ einfach von Hand erledigt werden kann.
Sie würden einen Co-Prozess starten mit:
exec 5>>|4 3>(cmd >&5 4<&- 5>&-) 5>&-
Was zuerst ein pipe(4,5)
(5 das Schreibende, 4 das Leseende) erstellt, dann fd 3 zu einer Pipe umleitet, zu einem Prozess, der mit seinem stdin am anderen Ende ausgeführt wird, und stdout zu der zuvor erstellten Pipe übergeht. Dann schließen wir das schreibende Ende dieser Pipe im übergeordneten Element, das wir nicht benötigen. Also haben wir jetzt in der Shell fd 3 mit dem stdin des cmd verbunden und fd 4 mit Pipes mit dem stdout des cmd verbunden.
Beachten Sie, dass das Close-On-Exec-Flag in 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<&-
Co-Prozesse können einfach mit Standard Named Pipes implementiert werden. Ich weiß nicht, wann genau Named Pipes eingeführt wurden, aber es ist möglich, dass ksh
Co-Prozesse entwickelt wurden (wahrscheinlich Mitte der 80er Jahre, ksh88 wurde 88 "veröffentlicht", aber ich glaube, es ksh
wurde einige Jahre zuvor intern bei AT & T verwendet das) 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 unkomplizierter, insbesondere wenn Sie mehr als einen Co-Prozess ausführen müssen. (Siehe Beispiele unten.)
Der einzige Vorteil bei der Verwendung coproc
besteht darin, dass Sie diese benannten Rohre nach der Verwendung nicht entfernen müssen.
Muscheln verwenden Rohre in einigen Konstrukten:
cmd1 | cmd2
,$(cmd)
,<(cmd)
, >(cmd)
.In diesen fließen die Daten zwischen verschiedenen Prozessen nur in eine Richtung.
Bei Co-Prozessen und Named Pipes kann es jedoch leicht zu Deadlocks kommen. Sie müssen nachverfolgen, bei welchem Befehl welcher Dateideskriptor geöffnet ist, um zu verhindern, dass einer geöffnet bleibt und einen Prozess am Leben hä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 nur eine Pipe voll ist.
expect
für das, wofür es entwickelt wurdeDer Hauptzweck von Co-Prozessen bestand darin, der Shell die Möglichkeit zu geben, mit Befehlen zu interagieren. Es funktioniert jedoch 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 er seine Ausgabe. Es wird also nichts ausgegeben, bis entweder das Dateiende angezeigt stdin
wird oder ein Puffer voller auszugebender Daten vorhanden ist. Nach der Ausgabe der Shell a\n
(nur 2 Bytes) read
blockiert die also auf unbestimmte Zeit, da sie darauf tr
wartet, dass die Shell weitere Daten sendet.
Kurz gesagt, Pipes eignen sich nicht für die Interaktion mit Befehlen. Co-Prozesse können nur verwendet werden, um mit Befehlen zu interagieren, die ihre Ausgabe nicht puffern, oder mit Befehlen, die angewiesen werden, ihre Ausgabe nicht zu puffern. Zum Beispiel stdbuf
mit einigen Befehlen auf neueren GNU- oder FreeBSD-Systemen.
Deshalb expect
oder zpty
Verwendung pseudo-Terminals statt. expect
ist ein Tool, das für die Interaktion mit Befehlen entwickelt wurde und das auch gut funktioniert.
Co-Prozesse können verwendet werden, um komplexere Rohrleitungen zu erstellen, als dies mit einfachen Mantelrohren möglich ist.
Diese andere Unix.SE-Antwort enthält ein Beispiel für eine Coproc-Verwendung.
Hier ist ein vereinfachtes Beispiel: Stellen Sie sich vor, Sie möchten eine Funktion, die eine Kopie der Ausgabe eines Befehls an drei andere Befehle weiterleitet und dann die Ausgabe dieser drei Befehle verkettet.
Alles mit Rohren.
Zum Beispiel: füttern die Ausgabe printf '%s\n' foo bar
zu tr a b
, sed 's/./&&/g'
und cut -b2-
wie etwas zu erhalten:
foo
bbr
ffoooo
bbaarr
oo
ar
Erstens ist es nicht unbedingt offensichtlich, aber es besteht die Möglichkeit eines Deadlocks, der bereits nach wenigen Kilobyte Daten auftritt.
Abhängig von Ihrer Shell treten dann verschiedene Probleme auf, die unterschiedlich gelöst werden müssen.
Zum Beispiel zsh
würden Sie 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 '%s\n' foo bar | f
Oben ist für die Co-Process-FDS das Flag "Close-On-Exec" gesetzt, jedoch nicht die, die von ihnen dupliziert wurden (wie in {o1}<&p
). Um Deadlocks zu vermeiden, müssen Sie sicherstellen, dass sie in Prozessen geschlossen sind, die sie nicht benötigen.
In ähnlicher Weise müssen wir eine Unterschale verwenden und exec cat
am Ende verwenden, um sicherzustellen, dass kein Schalenprozess über das Offenhalten eines Rohrs liegt.
Mit ksh
(hier ksh93
) müsste das sein:
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 '%s\n' foo bar | f
( Hinweis: Dies funktioniert nicht auf Systemen, ksh
die socketpairs
anstelle von verwendet werden pipes
, und auf Systemen , die /dev/fd/n
wie unter Linux funktionieren.)
In ksh
werden fds oben 2
mit dem Flag close-on-exec markiert, sofern sie nicht explizit in der Befehlszeile übergeben werden. Aus diesem Grund müssen wir die nicht verwendeten Dateideskriptoren nicht schließen wie mit zsh
- aber es ist auch der Grund, warum wir diesen neuen Wert von tun {i1}>&$i1
und verwenden müssen , um ihn weiterzugeben und ...eval
$i1
tee
cat
In bash
dieser kann nicht getan werden, da Sie die close-on-exec nicht vermeiden Flagge.
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 beginnen, auf Shell-Fehler zu stoßen.
Vergleichen Sie das oben Genannte 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 '%s\n' foo bar | f
Wenn Sie möchten , mit einem Befehl, den Einsatz zu interagieren expect
, oder zsh
‚s zpty
oder Named Pipes.
Wenn Sie ausgefallene Rohrleitungen verwenden möchten, verwenden Sie Named Pipes.
Co-Prozesse können einige der oben genannten Aufgaben ausführen, sollten jedoch darauf vorbereitet sein, bei nicht-trivialen Aufgaben ernsthaft am Kopf zu kratzen.
exec {tr[1]}>&-
scheint in der Tat mit neueren Versionen zu funktionieren und wird in einem CWRU / Changelog-Eintrag referenziert ( lassen Sie Wörter wie {array [ind]} als gültige Umleitung zu ... 2012-09-01). exec {tr[1]}<&-
(oder das korrektere >&-
Äquivalent, obwohl das keinen Unterschied macht, da es nur close()
beides erfordert) schließt nicht die Standardeingabe des Coprocs, sondern das schreibende Ende der Pipe zu diesem Coproc.
yash
.
mkfifo
ist, dass Sie sich keine Sorgen um die Rennbedingungen und die Sicherheit für den Pipezugang machen müssen. Sie müssen sich immer noch um die Deadlocks bei den Fifos sorgen.
stdbuf
Befehl kann dazu beitragen, zumindest einige von ihnen zu verhindern. Ich habe es unter Linux und bash benutzt. Jedenfalls glaube ich, dass @ StéphaneChazelas im Fazit richtig ist: Die "Head Scratching" -Phase endete für mich erst, als ich wieder auf Named Pipes umstieg.
Co-Prozesse wurden zuerst in einer Shell-Skriptsprache mit der ksh88
Shell (1988) und später zsh
vor 1993 eingeführt.
Die Syntax zum Starten eines Co-Prozesses unter ksh lautet command |&
. Von dort aus können Sie mit in die command
Standardeingabe schreiben und mit print -p
deren Standardausgabe lesen read -p
.
Mehr als ein paar Jahrzehnte später führte bash, dem diese Funktion fehlte, sie schließlich in der Version 4.0 ein. Leider wurde eine inkompatible und komplexere Syntax gewählt.
Unter Bash 4.0 und höher können Sie mit dem coproc
Befehl einen Co-Prozess starten , z.
$ coproc awk '{print $2;fflush();}'
Sie können dann etwas an das Kommando stdin übergeben:
$ echo one two three >&${COPROC[1]}
und lese awk Ausgabe mit:
$ read -ru ${COPROC[0]} foo
$ echo $foo
two
Unter ksh wäre das gewesen:
$ awk '{print $2;fflush();}' |&
$ print -p "one two three"
$ read -p foo
$ echo $foo
two
Was ist ein Coproc?
Es ist die Abkürzung für "Co-Process", was bedeutet, dass ein zweiter Prozess mit der Shell zusammenarbeitet. Es ist einem Hintergrundjob sehr ähnlich, der mit einem "&" am Ende des Befehls gestartet wird, mit der Ausnahme, dass anstelle der gleichen Standardeingabe und -ausgabe wie die übergeordnete Shell die Standard-E / A über eine spezielle Verbindung mit der übergeordneten Shell verbunden ist Rohrart FIFO genannt. Für Referenz hier klicken
Man startet einen coproc in zsh mit
coproc command
Der Befehl muss zum Lesen von stdin und / oder zum Schreiben von stdout vorbereitet sein, oder er ist als Coproc wenig nützlich.
Lesen Sie diesen Artikel hier. Er enthält eine Fallstudie zwischen exec und coproc
|
. (Das heißt, verwenden Sie Pipes in den meisten Shells und Socket-Paare in ksh93). Pipes und Socket-Paare sind first-in, first-out, sie sind alle FIFO. mkfifo
Erstellt Named Pipes, Coprozesse verwenden keine Named Pipes.
Hier ist ein weiteres gutes (und funktionierendes) Beispiel - ein einfacher Server, der in BASH geschrieben ist. Bitte beachte, dass du OpenBSD's brauchst netcat
, das klassische wird nicht funktionieren. Natürlich können Sie inet socket anstelle von unix one verwenden.
server.sh
:
#!/usr/bin/env bash
SOCKET=server.sock
PIDFILE=server.pid
(
exec </dev/null
exec >/dev/null
exec 2>/dev/null
coproc SERVER {
exec nc -l -k -U $SOCKET
}
echo $SERVER_PID > $PIDFILE
{
while read ; do
echo "pong $REPLY"
done
} <&${SERVER[0]} >&${SERVER[1]}
rm -f $PIDFILE
rm -f $SOCKET
) &
disown $!
client.sh
:
#!/usr/bin/env bash
SOCKET=server.sock
coproc CLIENT {
exec nc -U $SOCKET
}
{
echo "$@"
read
} <&${CLIENT[0]} >&${CLIENT[1]}
echo $REPLY
Verwendungszweck:
$ ./server.sh
$ ./client.sh ping
pong ping
$ ./client.sh 12345
pong 12345
$ kill $(cat server.pid)
$
bash 4.3.11
, Sie können jetzt schließen coproc Filedeskriptoren direkt, ohne die Notwendigkeit für einen Aux. Variable; in Bezug auf das Beispiel in Ihrer Antwortexec {tr[1]}<&-
jetzt funktionieren würde (um die coproc enge stdin, beachten Sie, dass Ihr Code (indirekt) zu schließen versucht{tr[1]}
mit>&-
, aber{tr[1]}
ist das die coproc stdin und muss mit geschlossen werden<&-
). Der Fix muss irgendwo dazwischen gekommen sein4.2.25
, was immer noch das Problem darstellt, und4.3.11
was nicht.