Odpowiedzi:
koprocesy są kshcechą (już w ksh88). zshma tę funkcję od samego początku (wczesne lata 90.), a dopiero niedawno została dodana bashw 4.0(2009).
Jednak zachowanie i interfejs różnią się znacznie między 3 powłokami.
Pomysł jest jednak ten sam: pozwala rozpocząć zadanie w tle i móc wysyłać dane wejściowe i czytać dane wyjściowe bez konieczności uciekania się do nazwanych potoków.
Odbywa się to za pomocą nienazwanych potoków z większością powłok i par gniazd z najnowszymi wersjami ksh93 w niektórych systemach.
W a | cmd | b, aprzekazuje dane cmdi bodczytuje dane wyjściowe. Działając cmdjako koproces, powłoka może być jednocześnie ai b.
W kshrozpoczniesz koproces jako:
cmd |&
Podajesz dane cmd, wykonując takie czynności jak:
echo test >&p
lub
print -p test
I czytaj cmdwyniki takich rzeczy jak:
read var <&p
lub
read -p var
cmdjest uruchamiany jako każdą pracę w tle, można użyć fg, bg, killna nim i przekazać ją przez %job-numberlub za pośrednictwem $!.
Aby zamknąć koniec, z którego czytany cmdjest potok , możesz wykonać:
exec 3>&p 3>&-
I aby zamknąć koniec odczytu drugiego potoku (ten cmdpisze do):
exec 3<&p 3<&-
Nie można rozpocząć drugiego koprocesu, chyba że najpierw zapiszesz deskryptory pliku potoku na innym dysku FDS. Na przykład:
tr a b |&
exec 3>&p 4<&p
tr b c |&
echo aaa >&3
echo bbb >&p
W zsh, koprocesy są prawie identyczne jak te w ksh. Jedyną prawdziwą różnicą jest to, że zshkoprocesy rozpoczynane są od coprocsłowa kluczowego.
coproc cmd
echo test >&p
read var <&p
print -p test
read -p var
Robić:
exec 3>&p
Uwaga: To nie przenosi coprocdeskryptora pliku do fd 3(jak w ksh), ale powiela go. Tak więc nie ma wyraźnego sposobu na zamknięcie rury zasilającej lub czytającej, inne rozpoczynając inną coproc .
Na przykład, aby zamknąć koniec podawania:
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
Oprócz koprocesów opartych na potoku zsh(od wersji 3.1.6-dev19, wydanej w 2000 r.) Ma konstrukcje oparte na pseudo-tty expect. Aby współdziałać z większością programów, koprocesy w stylu ksh nie będą działać, ponieważ programy zaczynają buforować, gdy ich wyjściem jest potok.
Oto kilka przykładów.
Rozpocznij koproces x:
zmodload zsh/zpty
zpty x cmd
(Tutaj cmdjest proste polecenie. Ale możesz robić bardziej wyszukane rzeczy za pomocą evallub funkcji.)
Wprowadź dane z procesu równoległego:
zpty -w x some data
Czytaj dane z jednoczesnego przetwarzania (w najprostszym przypadku):
zpty -r x var
Na przykład expectmoże czekać na dane wyjściowe z koprocesu pasujące do danego wzorca.
Składnia bash jest znacznie nowsza i opiera się na nowej funkcji ostatnio dodanej do ksh93, bash i zsh. Zapewnia składnię umożliwiającą obsługę dynamicznie przydzielanych deskryptorów plików powyżej 10.
bashoferuje podstawową coproc i rozszerzoną składnię .
Podstawowa składnia do rozpoczęcia koprocesu wygląda następująco zsh:
coproc cmd
W kshlub zsh, rury do i z koprocesu są dostępne za pomocą >&pi <&p.
Ale bashwewnątrz deskryptory plików potoku z koprocesu i drugiego potoku do koprocesu są zwracane w $COPROCtablicy (odpowiednio ${COPROC[0]}i ${COPROC[1]}. Więc…
Przesyłaj dane do koprocesu:
echo xxx >&"${COPROC[1]}"
Czytaj dane z koprocesu:
read var <&"${COPROC[0]}"
Dzięki podstawowej składni możesz jednocześnie uruchomić tylko jeden koproces.
W rozszerzonej składni możesz nazwać swoje koprocesy (jak w zshkoprocesach zpty):
coproc mycoproc { cmd; }
Polecenie musi być poleceniem złożonym. (Zauważ, jak przypomina powyższy przykład function f { ...; }.)
Tym razem deskryptory plików znajdują się w ${mycoproc[0]}i ${mycoproc[1]}.
Można uruchomić więcej niż jeden proces współpracy w czasie, ale trzeba zrobić dostać ostrzeżenie, kiedy rozpocząć proces współpracy, a jeden wciąż działa (nawet w trybie non-interaktywnym).
W przypadku korzystania z rozszerzonej składni można zamknąć deskryptory plików.
coproc tr { tr a b; }
echo aaa >&"${tr[1]}"
exec {tr[1]}>&-
cat <&"${tr[0]}"
Pamiętaj, że zamknięcie w ten sposób nie działa w wersjach bash wcześniejszych niż 4.3, w których musisz to napisać:
fd=${tr[1]}
exec {fd}>&-
Podobnie jak w kshi zshte deskryptory plików potoków są oznaczone jako close-on-exec.
Ale bashjedynym sposobem, aby przejść do tych wykonywanych poleceń jest powielać je do FDS 0, 1lub 2. Ogranicza to liczbę koprocesów, z którymi można współdziałać w ramach jednego polecenia. (Zobacz poniżej przykład.)
yashsam w sobie nie ma funkcji współprocesowej, ale tę samą koncepcję można wdrożyć dzięki funkcji przekierowywania potoku i procesu . yashma interfejs do pipe()wywołania systemowego, więc można tego dokonać stosunkowo łatwo ręcznie.
Rozpocząłbyś koproces z:
exec 5>>|4 3>(cmd >&5 4<&- 5>&-) 5>&-
Który najpierw tworzy pipe(4,5)(5 koniec zapisu, 4 koniec odczytu), a następnie przekierowuje fd 3 do potoku do procesu, który działa ze stdin na drugim końcu, a stdout przechodzi do utworzonego wcześniej potoku. Następnie zamykamy koniec pisania tego potoku w rodzicu, którego nie będziemy potrzebować. Więc teraz w powłoce mamy fd 3 podłączony do standardowego wejścia cmd i fd 4 podłączony do standardowego wyjścia cmd za pomocą rur.
Zauważ, że flaga close-on-exec nie jest ustawiona dla tych deskryptorów plików.
Aby karmić dane:
echo data >&3 4<&-
Aby odczytać dane:
read var <&4 3>&-
I możesz zamknąć FDS jak zwykle:
exec 3>&- 4<&-
Współprocesy można łatwo wdrożyć za pomocą standardowych nazwanych rur. Nie wiem, kiedy wprowadzono dokładnie nazwane potoki, ale możliwe jest, że kshpojawiły się koprocesy (prawdopodobnie w połowie lat 80., ksh88 został „wydany” w 88, ale sądzę, że kshbył używany wewnętrznie w AT&T kilka lat wcześniej to), co tłumaczy dlaczego.
cmd |&
echo data >&p
read var <&p
Można napisać za pomocą:
mkfifo in out
cmd <in >out &
exec 3> in 4< out
echo data >&3
read var <&4
Interakcja z nimi jest prostsza - szczególnie jeśli potrzebujesz uruchomić więcej niż jeden koproces. (Zobacz przykłady poniżej.)
Jedyną zaletą używania coprocjest to, że po użyciu nie trzeba czyścić nazwanych rur.
Pociski używają rur w kilku konstrukcjach:
cmd1 | cmd2 ,$(cmd) ,<(cmd) , >(cmd).W tych danych dane przepływają tylko w jednym kierunku między różnymi procesami.
Jednak dzięki jednoczesnym procesom i nazwanym potokom łatwo jest wpaść w impas. Musisz śledzić, które polecenie ma otwarty deskryptor pliku, aby zapobiec pozostawaniu otwartemu i utrzymywaniu procesu. Zakleszczenia mogą być trudne do zbadania, ponieważ mogą wystąpić nie deterministycznie; na przykład tylko wtedy, gdy wysyłanych jest tyle danych, ile potrzeba do wypełnienia jednej rury.
expectdo tego, do czego został zaprojektowanyGłównym celem koprocesów było zapewnienie powłoce sposobu interakcji z poleceniami. Jednak to nie działa tak dobrze.
Najprostsza z wymienionych wyżej form impasu:
tr a b |&
echo a >&p
read var<&p
Ponieważ dane wyjściowe nie trafiają do terminala, trbuforowane są dane wyjściowe. Więc nic nie wyświetli, dopóki nie zobaczy na końcu pliku stdinlub nie zgromadzi bufora pełnego danych do wydrukowania. Powyżej, po tym, jak powłoka ma wyjście a\n(tylko 2 bajty), readblokuje się na czas nieokreślony, ponieważ trczeka, aż powłoka wyśle mu więcej danych.
Krótko mówiąc, potoki nie nadają się do interakcji z poleceniami. Koprocesy mogą być używane tylko do interakcji z poleceniami, które nie buforują danych wyjściowych lub z poleceniami, których nie można buforować; na przykład za pomocą stdbufniektórych poleceń w najnowszych systemach GNU lub FreeBSD.
Właśnie dlatego expectlub zptyzamiast tego użyj pseudo-terminali. expectto narzędzie zaprojektowane do interakcji z poleceniami i robi to dobrze.
Współprocesy można wykorzystać do bardziej skomplikowanych prac hydraulicznych, niż pozwalają na to proste rury płaszczowe.
ta inna odpowiedź Unix.SE zawiera przykład użycia coproc.
Oto uproszczony przykład: Wyobraź sobie, że potrzebujesz funkcji, która przesyła kopię danych wyjściowych polecenia do 3 innych poleceń, a następnie łączy dane wyjściowe tych 3 poleceń.
Wszystko za pomocą rur.
Na przykład: nakarmić wyjście printf '%s\n' foo bardo tr a b, sed 's/./&&/g'i cut -b2-aby otrzymać coś takiego:
foo
bbr
ffoooo
bbaarr
oo
ar
Po pierwsze, niekoniecznie jest to oczywiste, ale istnieje możliwość impasu i zacznie się to dziać już po kilku kilobajtach danych.
Następnie, w zależności od powłoki, napotkasz wiele różnych problemów, które należy rozwiązać inaczej.
Na przykład zshzrobiłbyś to z:
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
Powyżej, fds koprocesów mają ustawioną flagę close-on-exec, ale nie te, które są z nich duplikowane (jak w {o1}<&p). Aby uniknąć zakleszczeń, musisz upewnić się, że są zamknięte w procesach, które ich nie potrzebują.
Podobnie musimy użyć podpowłoki i użyć jej exec catna końcu, aby upewnić się, że nie ma procesu powłoki leżącego na utrzymaniu otwartej rury.
Z ksh(tutaj ksh93) musiałoby to być:
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
( Uwaga: To nie zadziała w systemach, w których kshużywa socketpairszamiast pipes, i gdzie /dev/fd/ndziała jak w Linuksie.)
W ksh, fds powyżej 2są oznaczone flagą close-on-exec, chyba że są przekazywane jawnie w wierszu poleceń. Dlatego nie musimy zamykać nieużywanych deskryptorów plików, jak w przypadku zsh- ale właśnie dlatego musimy to zrobić {i1}>&$i1i użyć evaltej nowej wartości $i1, która ma zostać przekazana teei cat…
W bashtym nie da się zrobić, ponieważ nie można uniknąć Close-on-exec flagi.
Powyżej jest to stosunkowo proste, ponieważ używamy tylko prostych poleceń zewnętrznych. Staje się to bardziej skomplikowane, gdy zamiast tego chcesz użyć tam konstrukcji powłoki i zaczynasz napotykać błędy powłoki.
Porównaj powyższe z tym samym, używając nazwanych potoków:
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
Jeśli chcesz współpracować z poleceniem, użytkowania expectlub zsh„s zptylub nazwanych potoków.
Jeśli chcesz wykonać fantazyjną instalację wodociągową za pomocą rur, użyj nazwanych rur.
Współprocesy mogą wykonać niektóre z powyższych czynności, ale bądź przygotowany na poważne podrapanie się w głowę za wszystko, co nie jest łatwe.
exec {tr[1]}>&-wydaje się, że rzeczywiście działa z nowszymi wersjami i jest przywoływany we wpisie CWRU / changelog ( pozwól, aby słowa takie jak {array [ind]} były prawidłowe przekierowanie ... 01.09.2012). exec {tr[1]}<&-(lub bardziej poprawny >&-odpowiednik, ale to nie robi różnicy, ponieważ wymaga tylko close()obu), nie zamyka standardowego wejścia coproc, ale koniec zapisu potoku do tego coproc.
yash.
mkfifojest to, że nie musisz martwić się warunkami wyścigu i bezpieczeństwem dostępu do rur. Nadal musisz martwić się impasem z fifo.
stdbufpolecenie może pomóc w zapobieganiu przynajmniej niektórym z nich. Użyłem go pod Linuksem i bash. W każdym razie uważam, że @ StéphaneChazelas ma rację we Wniosku: faza „drapania się po głowie” zakończyła się dla mnie dopiero, gdy wróciłem do nazwanych potoków.
Koprocesy zostały po raz pierwszy wprowadzone w języku skryptowym powłoki z ksh88powłoką (1988), a później w zsh1993 r.
Składnia do rozpoczęcia współpracy w ramach procesu ksh jest command |&. Poczynając od tego, możesz pisać na commandstandardowe wejście print -pi czytać standardowe wyjście za pomocą read -p.
Ponad kilkadziesiąt lat później bash, któremu brakowało tej funkcji, ostatecznie wprowadził ją w wersji 4.0. Niestety wybrano niekompatybilną i bardziej złożoną składnię.
W wersji bash 4.0 i nowszych możesz uruchomić koproces z coprocpoleceniem, np .:
$ coproc awk '{print $2;fflush();}'
Następnie możesz przekazać coś do polecenia stdin w ten sposób:
$ echo one two three >&${COPROC[1]}
i czytaj wyjście awk za pomocą:
$ read -ru ${COPROC[0]} foo
$ echo $foo
two
Zgodnie z ksh byłoby to:
$ awk '{print $2;fflush();}' |&
$ print -p "one two three"
$ read -p foo
$ echo $foo
two
Co to jest „coproc”?
Jest skrótem od „koprocesu”, co oznacza drugi proces współpracujący z powłoką. Jest bardzo podobny do zadania w tle rozpoczynanego od „&” na końcu polecenia, z tym wyjątkiem, że zamiast współużytkować to samo standardowe wejście i wyjście co jego powłoka nadrzędna, jego standardowe wejście / wyjście jest połączone z powłoką nadrzędną za pomocą specjalnego rodzaj rury zwanej FIFO. Aby zapoznać się z informacjami, kliknij tutaj
Jeden uruchamia coproc w Zsh
coproc command
Polecenie musi być przygotowane do odczytu ze stdin i / lub zapisu na stdout, w przeciwnym razie nie jest zbyt przydatne jako coproc.
Przeczytaj ten artykuł tutaj, który zawiera studium przypadku między exec a coproc
|. (to znaczy używaj potoków w większości powłok i par gniazd w ksh93). rury i pary gniazd są pierwszymi, pierwszymi, wszystkie są FIFO. mkfifotworzy nazwane potoki, koprocesy nie używają nazwanych potoków.
Oto kolejny dobry (i działający) przykład - prosty serwer napisany w BASH. Pamiętaj, że potrzebujesz OpenBSD netcat, klasyczny nie zadziała. Oczywiście możesz użyć gniazda inet zamiast unixowego.
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
Stosowanie:
$ ./server.sh
$ ./client.sh ping
pong ping
$ ./client.sh 12345
pong 12345
$ kill $(cat server.pid)
$
bash 4.3.11wy, Mogę teraz zamknij deskryptory plików coproc bezpośrednio, bez konieczności stosowania AUX. zmienna; jeśli chodzi o przykład z twojej odpowiedzi,exec {tr[1]}<&-to teraz by działało (aby zamknąć stdin coproc; zwróć uwagę, że twój kod (pośrednio) próbuje zamknąć{tr[1]}używając>&-, ale{tr[1]}jest stdin coproc i musi zostać zamknięty<&-). Poprawka musiała pojawić się gdzieś pomiędzy4.2.25, co wciąż pokazuje problem, a4.3.11co nie.