comunicação entre múltiplos processos

13

Eu tenho um script bash, que executa a função manager () como um processo separado para x-times. Como é possível encaminhar mensagens para todos os processos manager () de dentro do script?

Eu li sobre pipes anônimos, mas não tenho idéia de como compartilhar as mensagens com ele. Tentei fazê-lo com pipes nomeados, mas parece que eu precisaria criar um pipe nomeado separado para cada processo?

Qual é a maneira mais elegante de fazer isso?

Aqui está o meu código até agora:

#!/bin/bash

manager () {
    while :
    do
        echo "read what has been passed to \$line"
    done
}

x=1
while [ $x -le 5 ]
do
  manager x &
  x=$(( $x + 1 ))
done


while :
do
while read line
do
  echo "What has been passed through the pipe is ${line}"
  # should pass $line to every manager process

done < $1
done

exit 0
Aljaz
fonte

Respostas:

25

O termo para o que você está tentando realizar é multiplexação .

Isso pode ser realizado facilmente no bash, mas requer alguns recursos mais avançados do bash.

Eu criei um script baseado no seu, que eu acho que faz o que você está tentando realizar. Vou explicar isso abaixo.

#!/bin/bash
manager() {
  while IFS= read -r line; do
    echo "manager[$1:$BASHPID]: $line"
  done
}

fds=()
for (( i=0; i<5; i++ )); do
  exec {fd}> >(manager $i)
  fds+=( $fd )
done

while IFS= read -r line; do
  echo "master: $line"
  for fd in "${fds[@]}"; do
    printf -- '%s\n' "$line" >&$fd
  done
done

manageré uma função bash que simplesmente lê de STDIN e grava seu identificador e a linha em STDOUT. Usamos em $BASHPIDvez de $$como $$não é atualizado para subshells (que é o que usaremos para iniciar manager.

fdsé uma matriz que retém os descritores de arquivo apontando para os tubos STDIN dos vários managers gerados.
Em seguida, fazemos um loop e criamos 5 processos de gerente. Eu uso a for (( ))sintaxe em vez da maneira que você estava fazendo, pois é mais limpo. Isso é específico do bash, mas várias das coisas que esse script faz são específicas do bash; portanto, é possível que você vá até o fim.
 

Em seguida, chegamos a exec {fd}> >(manager $i). Isso faz várias outras coisas específicas do bash.
O primeiro dos quais é {fd}>. Isso pega o próximo descritor de arquivo disponível no número 10 ou depois dele, abre um canal com o lado de gravação do canal atribuído a esse descritor de arquivo e atribui o número do descritor de arquivo à variável $fd.

O >(manager $i)lançamento manager $ie basicamente substitui o >(manager $i)caminho para um STDIN desse processo. Portanto, se managerfoi lançado como PID 1234, >(manager $i)pode ser substituído por /proc/1234/fd/0(isso depende do SO).

Portanto, supondo que o próximo número de descritor de arquivo disponível seja 10 e o gerenciador seja iniciado com o PID 1234, o comando exec {fd}> >(manager $i)basicamente se torna exec 10>/proc/1234/fd/0, e o bash agora tem o descritor de arquivo apontando para STDIN desse gerenciador.
Então, como o bash coloca esse número de descritor de arquivo $fd, adicionamos esse descritor à matriz fdspara uso posterior.
 

O resto é bem simples. O mestre lê uma linha do STDIN, itera sobre todos os descritores de arquivo $fdse envia a linha para o arquivo desciptor ( printf ... >&$fd).

 

O resultado fica assim:

$ /tmp/test.sh
hello
master: hello
manager[0:8876]: hello
manager[1:8877]: hello
manager[4:8880]: hello
manager[2:8878]: hello
manager[3:8879]: hello
world
master: world
manager[0:8876]: world
manager[1:8877]: world
manager[3:8879]: world
manager[2:8878]: world
manager[4:8880]: world

Onde eu digitei helloe world.

Patrick
fonte
@Patrick o é obras, mas você tem um erro de digitação, {fd} e deve ser de US $ {fd}
c4f4t0r
3
@ c4f4t0r Isso não é um erro de digitação #
Patrick as
@ Patrick no suse 11 "bash type.bash type.bash: linha 10: exec: {fd}: not found" mudei para exec $ {fd} e funciona assim
c4f4t0r
2
@ c4f4t0r A versão do bash no open suse 11 é bastante antiga (3.2). Esse recurso foi implementado no bash 4.0.
307 Patrick
Obrigado por muitas informações boas! Um resumo: eu posso entender por que você diria echo -- "$line"ou printf "%s\n" "$line"- mas por que você precisaria usar --quando o próximo argumento é codificado (e não começa com -)?
Scott
0

teee bash:

cat foo | tee >(manager) >(manager) >(manager) >(manager) >(manager) >/dev/null

Se o número de gerentes deve ser configurável ou se você deseja que a saída de diferentes gerenciadores não seja misturada:

export -f manager
cat foo | parallel --pipe --tee manager ::: {1..10}
Ole Tange
fonte