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.
Parabéns ao Julio Neves pela lógica e pela didática do artigo.
[]'s