it-swarm.com.de

Wie benutzt man den Befehl coproc in verschiedenen Shells?

Kann jemand ein paar Beispiele zur Verwendung von coproc geben?

84
slm

co-Prozesse sind eine ksh -Funktion (bereits in ksh88). zsh hatte die Funktion von Anfang an (Anfang der 90er Jahre), während sie in 4.0 (2009) nur zu bash hinzugefügt wurde.

Das Verhalten und die Schnittstelle unterscheiden sich jedoch erheblich zwischen den drei Schalen.

Die Idee ist jedoch dieselbe: Sie ermöglicht es, einen Job im Hintergrund zu starten und ihm Eingaben zu senden und seine Ausgaben zu lesen, ohne auf Named Pipes zurückgreifen zu müssen.

Dies geschieht mit unbenannten Rohren mit den meisten Schalen und Sockelpaaren mit neueren Versionen von ksh93 auf einigen Systemen.

In a | cmd | b gibt a Daten an cmd weiter und b liest seine Ausgabe. Wenn Sie cmd als Co-Prozess ausführen, kann die Shell sowohl a als auch b sein.

ksh co-prozesse

In ksh starten Sie einen Coprozess wie folgt:

cmd |&

Sie geben Daten an cmd weiter, indem Sie Folgendes tun:

echo test >&p

oder

print -p test

Und lesen Sie die Ausgabe von cmd mit folgenden Dingen:

read var <&p

oder

read -p var

cmd wird als beliebiger Hintergrundjob gestartet. Sie können fg, bg, kill darauf verwenden und über %job-number oder über $! darauf verweisen.

Um das Schreibende der Pipe zu schließen, aus der cmd liest, haben Sie folgende Möglichkeiten:

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 auf 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 sind Co-Prozesse nahezu identisch mit denen in ksh. Der einzige wirkliche Unterschied besteht darin, dass zsh Co-Prozesse mit dem Schlüsselwort coproc gestartet werden.

coproc cmd
echo test >&p
read var <&p
print -p test
read -p var

Tun:

exec 3>&p

Hinweis: Dadurch wird der Dateideskriptor coproc nicht nach fd 3 (wie in ksh) verschoben, sondern dupliziert. Es gibt also keine explizite Möglichkeit, die Zuführungs- oder Leseleitung zu schließen, andere starten andere coproc.

Zum Beispiel, um das Fütterungsende 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 verfügt zsh (seit 3.1.6-dev19, veröffentlicht im Jahr 2000) über 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.

Startet den Co-Prozess x:

zmodload zsh/zpty
zpty x cmd

(Hier ist cmd ein einfacher Befehl. Mit eval oder Funktionen können Sie jedoch schickere Dinge tun.)

Geben Sie Co-Prozessdaten ein:

zpty -w x some data

Co-Prozessdaten 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 bietet eine Syntax, mit der dynamisch zugewiesene Dateideskriptoren über 10 verarbeitet werden können.

bash bietet eine grundlegende coproc Syntax und eine erweiterte .

Grundlegende Syntax

Die grundlegende Syntax zum Starten eines Co-Prozesses sieht wie folgt aus: zsh:

coproc cmd

In ksh oder zsh wird auf die Pipes zum und vom Co-Prozess mit >&p und <&p zugegriffen.

In bash werden die Dateideskriptoren der Pipe vom Co-Prozess und der anderen Pipe zum Co-Prozess im Array $COPROC (bzw. ${COPROC[0]} und ${COPROC[1]}) zurückgegeben. Also…

Daten in den Co-Prozess einspeisen:

echo xxx >&"${COPROC[1]}"

Liest Daten aus dem Co-Prozess:

read var <&"${COPROC[0]}"

Mit der Basissyntax können Sie jeweils nur einen Co-Prozess starten.

Erweiterte Syntax

In der erweiterten Syntax können Sie Ihre Co-Prozesse benennen (wie in zsh zpty co-proccesses):

coproc mycoproc { cmd; }

Der Befehl muss ein zusammengesetzter Befehl sein. (Beachten Sie, dass das obige Beispiel an function f { ...; } erinnert.)

Dieses Mal befinden sich die Dateideskriptoren in ${mycoproc[0]} und ${mycoproc[1]}.

Sie können mehrere Co-Prozesse gleichzeitig starten - aber Sie erhalten eine Warnung, wenn Sie einen Co-Prozess starten, während einer noch ausgeführt wird (auch 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, in denen Sie es stattdessen schreiben müssen:

fd=${tr[1]}
exec {fd}>&-

Wie in ksh und zsh werden diese Pipe-Dateideskriptoren als "close-on-exec" markiert.

In bash besteht die einzige Möglichkeit, diese an ausgeführte Befehle zu übergeben, darin, sie an fds 0, 1 oder 2 zu duplizieren. Dies begrenzt die Anzahl der Co-Prozesse, 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 keine Co-Process-Funktion, aber das gleiche Konzept kann mit seiner Pipeline und implementiert werden ) Umleitungsfunktionen verarbeiten . yash hat eine Schnittstelle zum Systemaufruf pipe(), so dass solche Dinge dort relativ einfach von Hand erledigt werden können.

Sie würden einen Co-Prozess starten mit:

exec 5>>|4 3>(cmd >&5 4<&- 5>&-) 5>&-

Dadurch wird zuerst eine pipe(4,5) (5 das Schreibende, 4 das Leseende) erstellt, dann fd 3 an eine Pipe zu einem Prozess umgeleitet, der mit seinem stdin am anderen Ende ausgeführt wird, und stdout wird an die erstellte Pipe weitergeleitet vorhin. Dann schließen wir das Schreibende dieser Pipe im übergeordneten Element, das wir nicht benötigen. Jetzt haben wir in der Shell fd 3 mit dem stdin des cmd und fd 4 mit pipes mit dem stdout des cmd verbunden.

Beachten Sie, dass das Close-on-Exec-Flag für diese 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<&-

Warum sind sie nicht so beliebt?

kaum ein Vorteil gegenüber der Verwendung von benannten Rohren

Co-Prozesse können einfach mit Standard-Named-Pipes implementiert werden. Ich weiß nicht, wann genau benannte Pipes eingeführt wurden, aber es ist möglich, dass ksh Co-Prozesse entwickelt hat (wahrscheinlich Mitte der 80er Jahre wurde ksh88 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 besteht darin, dass Sie die genannten Pipes nach der Verwendung nicht bereinigen müssen.

deadlock-anfällig

Muscheln verwenden Rohre in einigen Konstrukten:

  • Shell Pipes: cmd1 | cmd2,
  • Befehlsersetzung: $(cmd),
  • und Prozesssubstitution: <(cmd), >(cmd).

In diesen fließen die Daten in nur einer Richtung zwischen verschiedenen Prozessen.

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 erhält. Deadlocks können schwierig zu untersuchen sein, da sie möglicherweise nicht deterministisch auftreten. 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 zur Interaktion mit Befehlen zu bieten. 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 die Ausgabe nicht an ein Terminal geht, puffert tr die Ausgabe. Es wird also nichts ausgegeben, bis entweder das Dateiende auf seinem stdin angezeigt wird oder ein Puffer voller Daten für die Ausgabe angesammelt wurde. Nachdem die Shell a\n (nur 2 Bytes) ausgegeben hat, wird read auf unbestimmte Zeit blockiert, da tr darauf 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 Befehlen, die angewiesen werden können, ihre Ausgabe nicht zu puffern. Beispiel: Verwenden Sie stdbuf mit einigen Befehlen auf neueren GNU- oder FreeBSD-Systemen).

Deshalb verwenden expect oder zpty stattdessen Pseudo-Terminals. expect ist ein Tool, das für die Interaktion mit Befehlen entwickelt wurde und das gut funktioniert.

Die Handhabung von Dateideskriptoren ist umständlich und schwer zu korrigieren

Co-Prozesse können verwendet werden, um komplexere Installationen durchzuführen, als dies mit einfachen Shell-Rohren möglich ist.

diese andere Unix.SE-Antwort hat 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 3 andere Befehle weiterleitet und dann die Ausgabe dieser 3 Befehle verkettet.

Alle mit Rohren.

Beispiel: Geben Sie die Ausgabe von printf '%s\n' foo bar an tr a b, sed 's/./&&/g' und cut -b2- weiter, um Folgendes zu erhalten:

foo
bbr
ffoooo
bbaarr
oo
ar

Erstens ist dies nicht unbedingt offensichtlich, aber es besteht die Möglichkeit eines Deadlocks, und dies beginnt bereits nach wenigen Kilobyte Daten.

Abhängig von Ihrer Shell treten dann verschiedene Probleme auf, die unterschiedlich angegangen werden müssen.

Mit zsh würden Sie dies beispielsweise 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 '%s\n' foo bar | f

Oben ist für die Co-Process-FDS das Close-On-Exec-Flag gesetzt, aber nicht diejenigen, 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 Subshell verwenden und am Ende exec cat verwenden, um sicherzustellen, dass kein Shell-Prozess darüber liegt, ein Rohr offen zu halten.

Bei 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, auf denen kshsocketpairs anstelle von pipes verwendet und auf denen /dev/fd/n wie folgt funktioniert unter Linux.)

In ksh werden fds über 2 mit dem Close-on-Exec-Flag markiert, sofern sie nicht explizit in der Befehlszeile übergeben werden. Aus diesem Grund müssen wir die nicht verwendeten Dateideskriptoren nicht wie bei zsh schließen, sondern auch {i1}>&$i1 und eval verwenden, damit der neue Wert von $i1 übergeben wird tee und cat

In bash ist dies nicht möglich, da Sie das Close-on-Exec-Flag nicht vermeiden können.

Oben ist es relativ einfach, da wir nur einfache externe Befehle verwenden. Es wird komplizierter, wenn Sie stattdessen Shell-Konstrukte verwenden möchten und auf Shell-Fehler stoßen.

Vergleichen Sie die obigen Angaben mit den genannten 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

Fazit

Wenn Sie mit einem Befehl interagieren möchten, verwenden Sie expect oder zshzpty oder Named Pipes.

Wenn Sie ausgefallene Klempnerarbeiten mit Rohren ausführen möchten, verwenden Sie benannte Rohre.

Co-Prozesse können einige der oben genannten Aufgaben ausführen, sind jedoch bereit, ernsthafte Kopfkratzer für nicht triviale Dinge auszuführen.

124

Co-Prozesse wurden zuerst in einer Shell-Skriptsprache mit der Shell ksh88 (1988) und später in zsh irgendwann vor 1993 eingeführt.

Die Syntax zum Starten eines Co-Prozesses unter ksh lautet command |&. Von dort aus können Sie mit print -p In die Standardeingabe command schreiben und die Standardausgabe mit read -p Lesen.

Mehr als ein paar Jahrzehnte später führte bash, dem dieses Feature fehlte, es schließlich in seiner Version 4.0 ein. Leider wurde eine inkompatible und komplexere Syntax ausgewählt.

Unter Bash 4.0 und höher können Sie einen Co-Prozess mit dem Befehl coproc starten, z.

$ coproc awk '{print $2;fflush();}'

Sie können dann auf folgende Weise etwas an den Befehl stdin übergeben:

$ echo one two three >&${COPROC[1]}

und lies awk output 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
7
jlliagre

Was ist ein "Coproc"?

Es ist die Abkürzung für "Co-Process", was einen zweiten Prozess bedeutet, der mit der Shell zusammenarbeitet. Es ist einem Hintergrundjob sehr ähnlich, der mit einem "&" am Ende des Befehls gestartet wurde, mit der Ausnahme, dass anstelle der gleichen Standardeingabe und -ausgabe wie bei der übergeordneten Shell die Standard-E/A über ein spezielles Element mit der übergeordneten Shell verbunden ist Art von Pipe namens FIFO.Für Referenz hier klicken

Man startet einen Coproc in zsh mit:

coproc command

Der Befehl muss darauf vorbereitet sein, von stdin zu lesen und/oder in stdout zu schreiben, oder er ist als Coproc nicht sehr nützlich.

Lesen Sie diesen Artikel hier Es enthält eine Fallstudie zwischen exec und coproc

0

Hier ist ein weiteres gutes (und funktionierendes) Beispiel - ein einfacher Server, der in BASH geschrieben wurde. Bitte beachten Sie, dass Sie OpenBSDs netcat benötigen würden, der klassische funktioniert nicht. Natürlich können Sie auch den Inet-Socket anstelle von Unix 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 "[email protected]"
    read
} <&${CLIENT[0]} >&${CLIENT[1]}

echo $REPLY

Verwendungszweck:

$ ./server.sh
$ ./client.sh ping
pong ping
$ ./client.sh 12345
pong 12345
$ kill $(cat server.pid)
$
0