Wiesz, nie jestem przekonany, że koniecznie potrzebujesz powtarzalnej pętli sprzężenia zwrotnego, gdy przedstawiają cię twoje diagramy, na tyle, na ile możesz użyć trwałego potoku między koprocesami . Z drugiej strony może się okazać, że nie ma zbyt dużej różnicy - po otwarciu linii w koprocesie można wdrożyć typowe pętle stylu, po prostu zapisując informacje i odczytując je, nie robiąc nic nadzwyczajnego.
Po pierwsze, wydaje się, że bc
jest to dla ciebie główny kandydat na koproces. Wbc
możesz define
funkcje, które mogą zrobić prawie to, o co prosisz w swoim pseudokodzie. Na przykład niektóre bardzo proste funkcje do wykonania tego mogą wyglądać:
printf '%s()\n' b c a |
3<&0 <&- bc -l <<\IN <&3
a=1; b=0; c=0;
define a(){ "a="; return (a = c+1); }
define b(){ "b="; return (b = 3*a); }
define c(){ "c="; return (c = s(b)); }
IN
... który wydrukowałby ...
b=3
c=.14112000805986722210
a=1.14112000805986722210
Ale oczywiście to nie trwa . Gdy tylko podpowłoka odpowiedzialna za printf
potok zostanie zamknięta (zaraz po printf
zapisie a()\n
na rurze), potok zostaje zerwany, a bc
wejście zamyka się, a także zamyka. To nie jest tak przydatne, jak mogłoby być.
@derobert wspomniał już o FIFO jak można to zrobić, tworząc plik potoku nazwanego za pomocą mkfifo
narzędzia. Są to w zasadzie tylko potoki, ale jądro systemu łączy wpis systemu plików z obydwoma końcami. Są to bardzo przydatne, ale byłoby miło, gdybyś mógł po prostu mieć fajkę bez ryzyka, że zostanie ona podsłuchana w systemie plików.
Tak się składa, że twoja skorupa często to robi. Jeśli używasz powłoki, która implementuje podstawianie procesów , masz bardzo prosty sposób na uzyskanie trwałego potoku - takiego, jaki możesz przypisać procesowi w tle, z którym możesz się komunikować.
W bash
przykład możesz zobaczyć, jak działa podstawienie procesu:
bash -cx ': <(:)'
+ : /dev/fd/63
Widzisz, to naprawdę jest zamiana . Podczas rozszerzania powłoka zastępuje wartość odpowiadającą ścieżce do łącza do potoku . Możesz z tego skorzystać - nie musisz ograniczać się do używania tego potoku tylko do komunikowania się z dowolnym procesem w obrębie()
samego podstawienia ...
bash -c '
eval "exec 3<>"<(:) "4<>"<(:)
cat <&4 >&3 &
echo hey cat >&4
read hiback <&3
echo "$hiback" here'
... które drukuje ...
hey cat here
Teraz wiem, że różne powłoki wykonują proces koprocesowania na różne sposoby - i że istnieje specyficzna składnia bash
do konfigurowania jednej (i prawdopodobnie także jednej zsh
) - ale nie wiem, jak te rzeczy działają. Po prostu wiem, że można użyć powyższej składni zrobić praktycznie to samo, bez wszystkich żmudną procedurę zarówno bash
i zsh
- i można to zrobić bardzo podobną cechę dash
i busybox ash
aby osiągnąć ten sam cel z tu-dokumentów (bo dash
ibusybox
zrobić tutaj- dokumenty zawierające potoki zamiast plików tymczasowych, jak robią to dwa pozostałe) .
Tak więc po zastosowaniu do bc
...
eval "exec 3<>"<(:) "4<>"<(:)
bc -l <<\INIT <&4 >&3 &
a=1; b=0; c=0;
define a(){ "a="; return (a = c+1); }
define b(){ "b="; return (b = 3*a); }
define c(){ "c="; return (c = s(b)); }
INIT
export BCOUT=3 BCIN=4 BCPID="$!"
... to jest najtrudniejsza część. A to jest fajna część ...
set --
until [ "$#" -eq 10 ]
do printf '%s()\n' b c a >&"$BCIN"
set "$@" "$(head -n 3 <&"$BCOUT")"
done; printf %s\\n "$@"
... które drukuje ...
b=3
c=.14112000805986722210
a=1.14112000805986722210
#...24 more lines...
b=3.92307618030433853649
c=-.70433330413228041035
a=.29566669586771958965
... i nadal działa ...
echo a >&"$BCIN"
read a <&"$BCOUT"
echo "$a"
... który właśnie trafia mi ostatnią wartość dla bc
„s a
zamiast wywołanie a()
funkcji go i wydruki zwiększyć ...
.29566669586771958965
W rzeczywistości będzie działać, dopóki go nie zabiję i nie zniszczę rur IPC ...
kill "$BCPID"; exec 3>&- 4>&-
unset BCPID BCIN BCOUT