Odpowiedzi:
koprocesy są ksh
cechą (już w ksh88
). zsh
ma tę funkcję od samego początku (wczesne lata 90.), a dopiero niedawno została dodana bash
w 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
, a
przekazuje dane cmd
i b
odczytuje dane wyjściowe. Działając cmd
jako koproces, powłoka może być jednocześnie a
i b
.
W ksh
rozpoczniesz koproces jako:
cmd |&
Podajesz dane cmd
, wykonując takie czynności jak:
echo test >&p
lub
print -p test
I czytaj cmd
wyniki takich rzeczy jak:
read var <&p
lub
read -p var
cmd
jest uruchamiany jako każdą pracę w tle, można użyć fg
, bg
, kill
na nim i przekazać ją przez %job-number
lub za pośrednictwem $!
.
Aby zamknąć koniec, z którego czytany cmd
jest potok , możesz wykonać:
exec 3>&p 3>&-
I aby zamknąć koniec odczytu drugiego potoku (ten cmd
pisze 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 zsh
koprocesy rozpoczynane są od coproc
słowa kluczowego.
coproc cmd
echo test >&p
read var <&p
print -p test
read -p var
Robić:
exec 3>&p
Uwaga: To nie przenosi coproc
deskryptora 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 cmd
jest proste polecenie. Ale możesz robić bardziej wyszukane rzeczy za pomocą eval
lub 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 expect
moż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.
bash
oferuje podstawową coproc
i rozszerzoną składnię .
Podstawowa składnia do rozpoczęcia koprocesu wygląda następująco zsh
:
coproc cmd
W ksh
lub zsh
, rury do i z koprocesu są dostępne za pomocą >&p
i <&p
.
Ale bash
wewnątrz deskryptory plików potoku z koprocesu i drugiego potoku do koprocesu są zwracane w $COPROC
tablicy (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 zsh
koprocesach 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 ksh
i zsh
te deskryptory plików potoków są oznaczone jako close-on-exec.
Ale bash
jedynym sposobem, aby przejść do tych wykonywanych poleceń jest powielać je do FDS 0
, 1
lub 2
. Ogranicza to liczbę koprocesów, z którymi można współdziałać w ramach jednego polecenia. (Zobacz poniżej przykład.)
yash
sam w sobie nie ma funkcji współprocesowej, ale tę samą koncepcję można wdrożyć dzięki funkcji przekierowywania potoku i procesu . yash
ma 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 ksh
pojawiły się koprocesy (prawdopodobnie w połowie lat 80., ksh88 został „wydany” w 88, ale sądzę, że ksh
był 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 coproc
jest 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.
expect
do 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, tr
buforowane są dane wyjściowe. Więc nic nie wyświetli, dopóki nie zobaczy na końcu pliku stdin
lub nie zgromadzi bufora pełnego danych do wydrukowania. Powyżej, po tym, jak powłoka ma wyjście a\n
(tylko 2 bajty), read
blokuje się na czas nieokreślony, ponieważ tr
czeka, 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ą stdbuf
niektórych poleceń w najnowszych systemach GNU lub FreeBSD.
Właśnie dlatego expect
lub zpty
zamiast tego użyj pseudo-terminali. expect
to 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 bar
do 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 zsh
zrobił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 cat
na 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 ksh
używa socketpairs
zamiast pipes
, i gdzie /dev/fd/n
działa jak w Linuksie.)
W ksh
, fds powyżej 2
są 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}>&$i1
i użyć eval
tej nowej wartości $i1
, która ma zostać przekazana tee
i cat
…
W bash
tym 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 expect
lub zsh
„s zpty
lub 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
.
mkfifo
jest to, że nie musisz martwić się warunkami wyścigu i bezpieczeństwem dostępu do rur. Nadal musisz martwić się impasem z fifo.
stdbuf
polecenie 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 ksh88
powłoką (1988), a później w zsh
1993 r.
Składnia do rozpoczęcia współpracy w ramach procesu ksh jest command |&
. Poczynając od tego, możesz pisać na command
standardowe wejście print -p
i 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 coproc
poleceniem, 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. mkfifo
tworzy 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.11
wy, 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.11
co nie.