Você sabe, não estou convencido de que você precise necessariamente de um ciclo de feedback repetitivo, como retratam seus diagramas, tanto quanto talvez você possa usar um pipeline persistente entre os coprocessos . Por outro lado, pode ser que não exista muita diferença - uma vez que você abre uma linha em um coprocessador, pode implementar loops típicos de estilo, escrevendo e lendo informações e lendo informações sem fazer nada fora do comum.
Em primeiro lugar, parece que você bc
é o principal candidato a um co-processo. Em bc
você pode define
funções que podem fazer praticamente o que você pede em seu pseudocódigo. Por exemplo, algumas funções muito simples para fazer isso podem parecer:
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
... o que imprimiria ...
b=3
c=.14112000805986722210
a=1.14112000805986722210
Mas é claro que não dura . Assim que o subconjunto responsável printf
pelo tubo do encerra (logo após a printf
gravação a()\n
no tubo), o tubo é desmontado e bc
a entrada é fechada e também é encerrada. Isso não é tão útil quanto poderia ser.
O @derobert já mencionou FIFO s, como pode ser obtido criando um arquivo de pipe nomeado com o mkfifo
utilitário. Também são essencialmente apenas canais, exceto que o kernel do sistema vincula uma entrada do sistema de arquivos às duas extremidades. Isso é muito útil, mas seria melhor se você pudesse ter um canal sem correr o risco de ser espionado no sistema de arquivos.
Por acaso, seu shell faz muito isso. Se você usa um shell que implementa a substituição do processo , você tem um meio muito simples de obter um canal duradouro - do tipo que você pode atribuir a um processo em segundo plano com o qual você pode se comunicar.
Em bash
, por exemplo, você pode ver como a substituição do processo funciona:
bash -cx ': <(:)'
+ : /dev/fd/63
Você vê que é realmente uma substituição . O shell substitui um valor durante a expansão que corresponde ao caminho para um link para um pipe . Você pode tirar proveito disso - você não precisa ser obrigado a usar esse canal apenas para se comunicar com qualquer processo executado dentro da ()
própria substituição ...
bash -c '
eval "exec 3<>"<(:) "4<>"<(:)
cat <&4 >&3 &
echo hey cat >&4
read hiback <&3
echo "$hiback" here'
... que imprime ...
hey cat here
Agora eu sei que diferentes shells executam o processo de coprocessamento de maneiras diferentes - e que existe uma sintaxe específica bash
para configurar uma (e provavelmente uma zsh
também) - mas não sei como essas coisas funcionam. Eu apenas sei que você pode usar a sintaxe acima para fazer praticamente a mesma coisa sem todo o rigmarole em ambos bash
e zsh
- e você pode fazer uma coisa muito semelhante dash
e busybox ash
alcançar o mesmo objetivo com os documentos aqui (porque dash
e busybox
faça aqui- documentos com tubos em vez de arquivos temporários, como os outros dois) .
Então, quando aplicado a 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="$!"
... essa é a parte mais difícil. E esta é a parte divertida ...
set --
until [ "$#" -eq 10 ]
do printf '%s()\n' b c a >&"$BCIN"
set "$@" "$(head -n 3 <&"$BCOUT")"
done; printf %s\\n "$@"
... que imprime ...
b=3
c=.14112000805986722210
a=1.14112000805986722210
#...24 more lines...
b=3.92307618030433853649
c=-.70433330413228041035
a=.29566669586771958965
... e ainda está em execução ...
echo a >&"$BCIN"
read a <&"$BCOUT"
echo "$a"
... o que me dá o último valor de bc
, em a
vez de chamar a a()
função para incrementá-lo e imprimi-lo ...
.29566669586771958965
De fato, ele continuará funcionando até que eu o mate e destrua os canos do IPC ...
kill "$BCPID"; exec 3>&- 4>&-
unset BCPID BCIN BCOUT