Visite também: UnderLinux ·  VivaOLinux ·  LinuxSecurity ·  Dicas-L ·  NoticiasLinux ·  SoftwareLivre.org ·  [mais] ·  Efetividade ·  Linux in Brazil ·  Floripa  

Botequim do Julio Neves: Named Pipes

Como anunciei ontem, o professor Julio Neves, autor do popular livro 'Programação Shell Linux', que recentemente teve publicada sua sexta edição, aceitou o convite do BR-Linux para dar continuidade aqui à sua série "Papo de Botequim", com artigos mensais sobre shell scripts. A série inicial, composta por 11 capítulos publicados pela Linux Magazine, encerrou recentemente. Mas como se trata de conteúdo que acho muitíssimo interessante, e o Julio é uma pessoa sempre disposta a compartilhar seus conhecimentos com a comunidade, achei que valia a pena oferecer um mecanismo que permitisse a continuação dessa disseminação de conhecimento.

Bem-vindo a bordo, Julio! O BR-Linux fica honrado de poder contar com tua colaboração de forma regular.

Veja abaixo a íntegra do primeiro artigo da nova série. Para ver mais material sobre shell publicado pelo Julio, visite www.julioneves.com - lá tem até um livro completo sobre Shell (é a coletânea dos artigos anteriores do Papo de Botequim, com 154 páginas A-4).


Named Pipes


por Júlio Neves

Um outro tipo de pipe é o named pipe, que também é chamado de FIFO. FIFO é um acrônimo de "First In First Out" que se refere à propriedade em que a ordem dos bytes entrando no pipe é a mesma que a da saída. O name em named pipe é, na verdade, o nome de um arquivo. Os arquivos tipo named pipes são exibidos pelo comando ls como qualquer outro, com poucas diferenças:


$ ls -l fifo1
prw-r-r-- 1 julio dipao 0 Jan 22 23:11 fifo1|


o p na coluna mais à esquerda indica que fifo1 é um named pipe. O resto dos bits de controle de permissões, quem pode ler ou gravar o pipe, funcionam como um arquivo normal.

Nos sistemas mais modernos uma barra vertical (|) colocado ao fim do nome do arquivo, é outra dica, e nos sistemas LINUX, onde a opção de cor está habilitada, o nome do arquivo é escrito em vermelho por default.

Nos sistemas mais antigos, os named pipes são criados pelo programa mknod, normalmente situado no diretório /etc. Nos sistemas mais modernos, a mesma tarefa é feita pelo mkfifo. O programa mkfifo recebe um ou mais nomes como argumento e cria pipes com estes nomes. Por exemplo, para criar um named pipe com o nome pipe1, faça:


$ mkfifo pipe1


Exemplo:

Como sempre, a melhor forma de mostrar como algo funciona é dando exemplos. Suponha que nós tenhamos criado o named pipe mostrado anteriormente. Em outra sessão ou uma console virtual, faça:


$ ls -l > pipe1


e em outra, faça:


$ cat < pipe1


Voilá! A saída do comando executado na primeira console foi exibida na segunda. Note que a ordem em que os comandos ocorreram não importa.

Se você prestou atenção, reparou que o primeiro comando executado, parecia ter "pendurado". Isto acontece porque a outra ponta do pipe ainda não estava conectada, e então o kernel suspendeu o primeiro processo até que o segundo "abrisse" o pipe. Para que um processo que usa pipe não fique em modo de wait, é necessário que em uma ponta do pipe tenha um processo "tagarela" e na outra um "ouvinte".

Uma aplicação muito útil dos named pipes é permitir que programas sem nenhuma relação possam se comunicar entre si, os named pipes também são usados para sincronizar processos, já que em um determinado ponto você pode colocar um processo para "ouvir" ou para "falar" em um determinado named pipe e ele daí só sairá, se outro processo "falar" ou "ouvir" aquele pipe.

Exemplo:

Suponha que você tenha uma CGI escrita em Shell e nela tenha uma função para contar os hits na sua URL. Ela poderia ser assim:


function Contador
{
HITS=$(cat arq.hits 2> /dev/null || echo 0)
echo $((++HITS)) > arq.hitshits
}


Parece complicado mas é simples. Vamos por partes:

1.Na construção $(cat arq.hits 2> /dev/null || echo 0), o $(...) serve para dizer ao Shell para executar primeiro o que está entre parênteses (se não fizéssemos isso, primeiro ele faria a atribuição definida pelo sinal de igual). Com o cat arq.hits 2> /dev/null || echo 0 estamos dizendo ao Shell para listar o conteúdo de arq.hits (cat arq.hits) mandando uma eventual mensagem de erro para /dev/null (isso só ocorreria se arq.hits não existisse) em caso de erro desta instrução (||) um zero seria produzido (echo 0), isto é, no final, esta construção atribuiria à variável HITS o conteúdo de arq.hits e caso ele não existisse o valor atribuído seria zero;

2.A construção $((++HITS)) serve para fazermos contas com inteiros e desta forma estaríamos pré-incrementando a variável e gravando de volta este valor no arquivo.
Até aí tudo bem, mas suponha que dois caras estejam acessando a sua URL quase que simultaneamente. Neste caso você perderia um hit. Para evitar que isso aconteça, o programa final poderia ter a seguinte forma:


$ cat contahits
#!/bin/bash
#
# Programa que ficará em background
# contando hits de uma página
#
function Contador
{
HITS=$(cat arq.hits 2> /dev/null || echo 0)
echo $((++HITS)) > arq.hitshits
}

# Corpo do programa

while true
do
cat arq.pipe
Contador
done


Supondo que arq.pipe seja um named pipe, o programa ficará em waiting ("ouvindo") no comando cat arq.pipe. Quando houvesse um hit, a sua CGI (que estaria rodando em foreground) faria:

echo qualquer coisa > arq.pipe

Agora sim, botamos um cara para "falar" no pipe e, desta forma, temos um processo "falando" e outro "ouvindo", e então está estabelecida a ponte mágica que permite que o processo em background saia do estado de wait e incremente o arquivo.

Suponha que neste ínterim, outro cara chegue à sua URL e o processo em foreground queira fazer o echo ("falar") em arq.pipe. Como o processo em background ainda está atualizando arq.hits, ninguém estára "ouvindo" o pipe e o primeiro ficará em wait até que o segundo volte para o loop do while, quando começará tudo novamente, sem perder nenhum hit. É devido a este enfileiramento causado que o named pipe também é conhecido como FIFO.

Até aqui tudo bem, mas agora vou mostrar algo não muito conhecido. Para começar repare que o Shell também usa os named pipes de uma maneira bastante singular, que é a substituição de processos (process substitution). Uma substituição de processos ocorre quando você põe um menor que (<) ou um maior que (>) grudado na frente do parêntese da esquerda. Teclando-se o comando:


$ cat <(ls -l)


Resultará no comando ls -l executado em um subshell como é normal, porém redirecionará a saída para um named pipe temporário, que o Shell cria, nomeia e depois remove. Então o cat terá um nome de arquivo válido para ler (que será este named pipe e cujo dispositivo lógico associado é /dev/fd/63), e teremos a mesma saída que a gerada pela listagem do ls -l, porém dando um ou mais passos que o usual.

Como poderemos constatar isso? Fácil... Veja o comando a seguir:


$ ls -l >(cat)
l-wx------ 1 jneves jneves 64 Aug 27 12:26 /dev/fd/63 -> pipe:[7050]


É... Realmente é um named pipe.

Você deve estar pensando que isto é uma maluquice de nerd, né? Então suponha que você tenha dois diretórios: dir e dir.bkp e deseja saber se os dois estão iguais (aquela velha dúvida: será que meu backup está atualizado?). Basta comparar os dados dos arquivos nos dois diretórios com o comando cmp, fazendo:


$ cmp <(ls -l dir/*) <(ls -l dir.bkp/*) || echo backup furado


da forma acima eu comparei somente as saídas dos ls, porque caso o backup não estivesse atualizado, haveria diferença entre os tamanhos e/ou as datas dos arquivos.
Como sou desconfiado, quero agora comparar linha-a-linha todos os arquivos de cada um dos diretórios. Então faço:


$ cmp <(cat dir/*) <(cat dir.bkp/*) >/dev/null || echo backup furado


A seguir um exemplo meramente didático, mas são tantos os comandos que produzem mais de uma linha de saída, que serve como guia para outros. Então veja este fragmento de código que serve para gerar uma listagem dos meus arquivos, numerando-os e ao final dar o total de arquivos do diretório corrente:


while read arq
do
((i++)) # assim nao eh necessario inicializar i
echo "$i: $arq"
done < <(ls)
echo "No diretorio corrente (`pwd`) existem $i arquivos"


Tá legal, eu sei que existem outras formas de executar a mesma tarefa. Mas tente fazer usando while, sem usar substituição de processos que você verá que este método é muito melhor.

Comentários dos leitores

Os comentários abaixo são responsabilidade de seus autores e não são revisados ou aprovados pelo BR-Linux. Consulte os Termos de uso para informações adicionais. Esta notícia foi arquivada, não será possível incluir novos comentários.
Comentário de MnB Linuxer
Muito bom: Ótimo ... Vejo que é possível fazer muitas coisas úteis com esses fifos.
Parabéns ao Julio Neves pela lógica e pela didática do artigo.
[]'s
Comentário de nemesis
ótima introdução!: obrigado pelo insight, Júlio! nada como a simplicidade de um shell contra a insanidade que toma conta de desenvolvimento de software moderno...

;; ((lambda (x) x) "Isto é um comentário e não será executado nunca")

Comentário de bogus
contahits: Seguindo o artigo do Julio Neves, é preciso substituir:

echo $((++HITS)) > arq.hitshits

por:

echo $((++HITS)) > arq.pipe
Comentário de silvio
Bogus, : Bogus,

Se eu entendi direito, o arq.pipe é na verdade só um "trigger" para chamar a função de forma garantida já que ele bloqueia se só uma das partes estiver no pipe.

De fato, tem um errinho nesta parte do shell mesmo: tem um "hits" a mais no nome do arquivo (nada que um 's/arq.hitshits/ar.hits' não resolva).

Parabéns ao Júlio pelo artigo, já havia usado named pipes antes mas nunca com essa profundidade. :)
Comentário de Julio C. Neves
Contahits: Fala Bogus,
a razão não é minha nem sua :( pois o certo seria:

echo $((++HITS)) > arq.hits

Creio que escrevi arq.hitshits em função de uma gageira temporária (/tmp/gageira) que me acomente em alguns artigos :)

Não adiantaria nada mandar a saída para arq.pipe, pq o objetivo desta função é somente incrementar arq.hits.
Comentário de Julio C. Neves
Resposta ao Sílvio: Sílvio disse:
=============================
Se eu entendi direito, o arq.pipe é na verdade só um "trigger" para chamar a função de forma garantida já que ele bloqueia se só uma das partes estiver no pipe.
=============================
Se eu entendi direito o que vc escreveu, vc entendeu direito o que eu escrevi. :)

É isso mesmo, acertou na mosca e juro como nunca havia imaginado um named pipe como um trigger, mas é!

Sílvio disse:
=============================
De fato, tem um errinho nesta parte do shell mesmo: tem um "hits" a mais no nome do arquivo (nada que um 's/arq.hitshits/ar.hits' não resolva).
=============================
É isso aí mesmo. Foi uma gagueira temporária que está em /tmp/gagueira :). Era só para ver se vc estava prestando atenção no artigo :)

Sílvio disse:
=============================
Parabéns ao Júlio pelo artigo, já havia usado named pipes antes mas nunca com essa profundidade. :)
=============================
Obrigado pelos parabens, mas como sabemos que pipe em inglês é tubo, para ser mais profundo basta virar o pipe para baixo. :)
Comentário de bogus
Erro meu: É isso mesmo. Eu é que não li direito o artigo e fiz confusão. Na leitura rápida que fiz, ainda não compreendo porque, fiquei achando que o contahits seria o cgi e que ele é que estaria encarregado de “liberar” o pipe. Óbvio que como o nome bem indica ele apenas conta hits. :)

Antes que eu esqueça, parabéns pela série de artigos. Fico feliz que continuem sendo divulgados por aqui.

BR-Linux.org
Linux® levado a sério desde 1996. Notícias, dicas e tutoriais em bom português sobre Linux e Código Aberto. "A página sobre software livre mais procurada no Brasil", segundo a Revista Isto É.
Expediente
Sobre o BR-Linux
Enviar notícia ou release
Contato, Termos de uso
FAQ, Newsletter, RSS
Banners e selos
Anunciar no BR-Linux
BR-Linux apóia
LinuxSecurity, Tempo Real
Suporte Livre, Drupal
Verdade Absoluta
Pandemonium
Efetividade, Floripa.net
sites da comunidade
Ajuda
Moderação
Flames: não responda!
Publicar seu texto
Computador para Todos
Notícias pré-2004
Tutoriais, HCL pré-2004