GNU/Linux >> LINUX-Kenntnisse >  >> Linux

Wie verwende ich den Befehl Coproc in verschiedenen Shells?

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.

markiert

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.)

Verwandt:Führen Sie eine nicht vertrauenswürdige Anwendung sicher über den Befehl sandbox-exec aus?

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.


Linux
  1. So verwenden Sie den Linux-Befehl sed

  2. So verwenden Sie den Linux-Grep-Befehl

  3. So verwenden Sie den Verlaufsbefehl unter Linux

  4. So verwenden Sie den id-Befehl unter Linux

  5. So verwenden Sie den Befehl „screen“ unter Linux

So verwenden Sie den nmap-Befehl

So verwenden Sie den fd-Befehl auf einem Linux-System

Wie verwende ich den wget-Befehl unter Linux?

Wie verwende ich den xargs-Befehl unter Linux?

So verwenden Sie den RPM-Befehl unter Linux

So verwenden Sie den which-Befehl in Linux