Você sabe, não estou convencido de que você precisa necessariamente de um loop de feedback repetitivo como seus diagramas retratam, talvez você possa usar um pipeline persistente entre coprocessos . Então, novamente, pode ser que não exista muita diferença - uma vez que você abre uma linha em um coprocess, você pode implementar loops de estilo típicos apenas escrevendo informações e lendo informações dele sem fazer nada muito fora do comum.
Em primeiro lugar, parece que bc
é um candidato ideal para um coprocessamento para você. Em bc
você pode define
funções que podem fazer praticamente o que você pede no 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, isso não dura . Assim que o subshell encarregado do pipe printf
terminar (logo após printf
gravar a()\n
no pipe) o pipe será demolido e a entrada bc
fechará também sai. Isso não é tão útil quanto poderia ser.
@derobert já mencionou FIFO s como pode ser obtido criando um arquivo pipe nomeado com o utilitário mkfifo
. Estes são essencialmente apenas pipes, exceto que o kernel do sistema liga uma entrada do sistema de arquivos para ambas as extremidades. Estes são muito úteis, mas seria melhor se você pudesse apenas ter um pipe sem correr o risco de ser bisbilhotado no sistema de arquivos.
Quando isso acontece, seu shell faz muito isso. Se você usa um shell que implementa substituição de processo , você tem um meio muito direto de obter um canal duradouro - do tipo que você pode atribuir a um processo em segundo plano com o qual 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 pipe 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 fazem o coprocess de diferentes maneiras - e que existe uma sintaxe específica em bash
para configurar um (e provavelmente um para zsh
também) - mas eu não sei como essas coisas funcionam. Eu só sei que você pode usar a sintaxe acima para fazer praticamente a mesma coisa sem toda a ladainha em bash
e zsh
- e você pode fazer uma coisa muito semelhante em dash
e busybox ash
para alcançar o mesma finalidade com here-documents (porque dash
e busybox
fazem aqui - documentos com pipes em vez de arquivos temporários como os outros dois fazem) .
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 difícil. E essa é 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"
... que apenas me pega o último valor para bc
' a
ao invés de chamar a função a()
para incrementá-la e imprimir ...
.29566669586771958965
Ele continuará a rodar, na verdade, até que eu o mate e derrube seus tubos IPC ...
kill "$BCPID"; exec 3>&- 4>&-
unset BCPID BCIN BCOUT