você está aqui: Home  → Colunistas  →  Cantinho do Shell

 

Bash - Coprocessos

Colaboração: Júlio Cezar Neves

Data de Publicação: 01 de Novembro de 2010

A partir da versão 4.0, o Bash incorporou o comando coproc. Este novo intrínseco (builtin) permite dois processos assíncronos se comunicarem e interagirem. Como cita Chet Ramey no Bash FAQ, ver. 4.01:

"Há uma nova palavra reservada, coproc, que especifica um coprocesso: um comando assíncrono que é executado com 2 pipes conectados ao Shell criador. coproc pode receber nome. Os descritores dos arquivos de entrada e saída e o PID do coprocesso estão disponíveis para o Shell criador em variáveis com nomes específicos do coproc."

George Dimitriu explica:

"coproc é uma facilidade usada na substituição de processos que agora está publicamente disponível."

A comprovação do que disse Dimitriu não é complicada, quer ver? Veja a substituição de processos a seguir:


$ cat <(echo xxx; sleep 3; echo yyy; sleep 3)


Viu?! O cat não esperou pela conclusão dos comandos entre parênteses, mas foi executado no fim de cada um deles. Isso aconteceu porque estabeleceu-se um pipe temporário/dinâmico e os comandos que estavam sendo executados, mandavam para ele as suas saídas, que por sua vez as mandava para a entrada do cat.

Isso significa que os comandos desta substituição de processos rodaram paralelos, sincronizando somente nas saídas dos echo com a entrada do cat.

A sintaxe de um coprocesso é:


coproc [nome] cmd redirecionamentos


Isso criará um coprocesso chamado nome. Se nome for informado, cmd deverá ser um comando composto. Caso contrário (no caso de nome não ser informado), cmd poderá ser um comando simples ou composto.

Quando um coproc é executado, ele cria um vetor com o mesmo nome nome no Shell criador. Sua saída padrão é ligada via um pipe a um descritor de arquivo associado à variável ${nome[0]} à entrada padrão do Shell pai (lembra que a entrada padrão de um processo é sempre associada por default ao descritor zero?). Da mesma forma, a entrada do coproc é ligada à saída padrão do script, via pipe, a um descritor de arquivos chamado ${nome[1]}.

Assim, simplificando, vemos que o script mandará dados para o coproc pela variável ${nome[0]}, e receberá sua saída em ${nome[1]}.

Note que estes pipes serão criados antes dos redirecionamentos especificados pelo comando, já que eles serão as entradas e saídas do coprocesso.

A partir daqui, vou detalhar rapidamente uns estudos que fiz. Isso contém um pouco de divagações e muita teoria. Se você pular para depois desses ls's, não perderá nada, mas se acompanhar, pode ser bom para a compreensão do mecanismo do coproc.

Após colocar um coproc rodando, se ele está associado a um descritor de arquivo, vamos ver o que tem ativo no diretório correspondente:


$ ls -l /dev/fd
lrwxrwxrwx 1 root root 13 2010-01-06 09:31 /dev/fd -> /proc/self/fd


Hummm, é um link para o /proc/self/fd... O que será que tem lá?


$ ls -l /proc/self/fd
total 0
lrwx------ 1 julio julio 64 2010-01-06 16:03 0 -> /dev/pts/0
lrwx------ 1 julio julio 64 2010-01-06 16:03 1 -> /dev/pts/0
lrwx------ 1 julio julio 64 2010-01-06 16:03 2 -> /dev/pts/0
lr-x------ 1 julio julio 64 2010-01-06 16:03 3 -> /proc/3127/fd


Epa, que o 0, 1 e 2 apontavam para /dev/pts/0 eu já sabia, pois são a entrada padrão, saída padrão e saída de erros padrão apontando para o pseudo terminal corrente, mas quem será esse maldito device 3? Vejamos:


$ ls -l /proc/$$/fd ($$ É o PID do Shell corrente)

total 0
lr-x------ 1 julio julio 64 2010-01-06 09:31 0 -> /dev/pts/0
lrwx------ 1 julio julio 64 2010-01-06 09:31 1 -> /dev/pts/0
lrwx------ 1 julio julio 64 2010-01-06 09:31 2 -> /dev/pts/0
lrwx------ 1 julio julio 64 2010-01-06 16:07 255 -> /dev/pts/0
l-wx------ 1 julio julio 64 2010-01-06 16:07 54 -> pipe:[168521]
l-wx------ 1 julio julio 64 2010-01-06 16:07 56 -> pipe:[124461]
l-wx------ 1 julio julio 64 2010-01-06 16:07 58 -> pipe:[122927]
lr-x------ 1 julio julio 64 2010-01-06 16:07 59 -> pipe:[168520]
l-wx------ 1 julio julio 64 2010-01-06 16:07 60 -> pipe:[121302]
lr-x------ 1 julio julio 64 2010-01-06 16:07 61 -> pipe:[124460]
lr-x------ 1 julio julio 64 2010-01-06 16:07 62 -> pipe:[122926]
lr-x------ 1 julio julio 64 2010-01-06 16:07 63 -> pipe:[121301]


Epa, aí estão os links apontando para os pipes. Esse monte de arquivo de pipe que foi listado, deve ser porque estava testando exaustivamente essa nova facilidade do Bash.

Para terminar esta teoria chata, falta dizer que o PID do Shell gerado para interpretar o coproc pode ser obtido na variável $nome_PID e o comando wait pode ser usado para esperar pelo fim do coprocesso. O código de retorno do coprocesso ($?) é o mesmo de cmd.

Exemplos:

Vamos começar com o mais simples: um exemplo sem nome e direto no prompt:


$ coproc while read Entra coproc ativo
> do
> echo
-=-=- $Entra -=-=-
> done
[2] 3030
$ echo Olá >&${COPROC[1]} Manda Olá para a pipe da saída
$ read -u ${COPROC[0]} Sai Lê do pipe da entrada
$ echo $Sai
-=-=- Olá -=-=-
$ kill $COPROC_PID Isso não pode ser esquecido...


Como você viu, o vetor COPROC, está associado a dois pipes; o ${COPROC[1]} que contém o endereço do pipe de saída, e por isso a saída do echo está redirecionada para ele e o ${COPROC[0]} que contém o endereço do pipe de entrada, e por isso usamos a opção -u do<samp>read</samp> que lê dados a partir de um descritor de arquivo definido, ao invés da entrada padrão.

Como o coprocesso utilizava a sintaxe sem nome, o padrão do nome do vetor é COPROC.

Só mais uma teoriazinha chata:


$ echo ${COPROC[@]} Lista todos os elementos do vetor
59 54


Como você viu ${COPROC[0]} estava usando o pipe apontado por /proc/$$/fd/59 e ${COPROC[1]} usava /proc/$$/fd/54.

Agora chega de teoria mesmo! Vamos agora usar nome neste mesmo exemplo, para ver que pouca coisa muda:


$ coproc teste {
> while read Entra
> do
> echo -=-=- $Entra -=-=-
> done
> }
[6] 3192
$ echo Olá &${teste[1]}
$ read -u ${teste[0]} Sai
$ echo $Sai
-=-=- Olá -=-=-
$ kill $teste_PID


Nesse momento, é bom mostrar uma coisa interessante: Quais são os processos em execução?


$ ps Somente um Bash em execução

PID TTY TIME CMD
1900 pts/0 00:00:01 bash
2882 pts/0 00:00:00 ps


Vamos executar 2 coprocessos simultâneos:


$ coproc nome1 { Coprocesso nome1
> while read x
> do
> echo $x
> done; }
[1] 2883

$ coproc nome2 { Coprocesso nome2
> while read y
> do
> echo $y
> done; }
bash: aviso: execute_coproc: coproc [2883:nome1] still exists
[2] 2884


Xiiii! Acho que deu zebra! Mas será que deu mesmo? Repare que além do PID 2883 de nome1,</samp> ele também me devolveu o PID 2884, que deve ser de nome2. Vamos ver o que está acontecendo:


$ ps

PID TTY TIME CMD
1900 pts/0 00:00:01 bash Esse já existia
2883 pts/0 00:00:00 bash Esse está executando nome1
2884 pts/0 00:00:00 bash Esse está executando nome2
2885 pts/0 00:00:00 ps


Parece que foi só um aviso, pois os dois PIDs informados quando iniciamos os dois coprocessos, estão ativos. Então vamos testar esses 2 caras:


$ echo xxxxxxxxx >&${nome1[1]} Mandando cadeia para nome1
$ echo yyyyyyyyy >&${nome2[1]} Mandando cadeia para nome2
$ read -u ${nome1[0]} Recebe
$ echo $Recebe
xxxxxxxxx
$ read -u ${nome2[0]} Recebe
$ echo $Recebe
yyyyyyyyy
$ kill $nome1_PID
$ kill $nome2_PID


Error: No site found with the domain '705839.zoqxoq1s.asia' (Learn more)