Programação Shell Linux, com Júlio Neves e Rubens Queiroz

Workshop Programação Shell Linux - Parte 2

Sumário

2.1. Download de arquivos

2.2. Envio de mensagens para múltiplos destinatários

2.3. Verificar o funcionamento de servidores

2.4. Teste de funcionamento dos serviços

2.5. A diretiva case

2.6. Extração de endereços de email de arquivos de log

2.7. Configuração automática do firewall

2.8. Conclusão

Workshop Programação Shell Linux - Parte 2

Hoje veremos diversos exemplos do uso de estruturas de laço e decisão em Shell Bash. Estes exemplos foram recolhidos de situações reais com as quais lidamos ao longo dos anos. Você poderá ver que a síntese dos comandos é praticamente a mesma, e verá também como é facílimo replicar estas estruturas para resolver os seus problemas.

Então, vamos lá!

Retornar ao sumário

2.1. Download de arquivos

A CBN costumava publicar as gravações de seus programas na web, em um formato que obedecia a uma sintaxe, com o seguinte formato:

coluna_AAMMDD.mp3 max_041102.mp3 

Por exemplo, os podcasts do Max Gehringer tinham o formato max_041102.mp3.

Para baixar todos os podcasts do Max Gehringer, desde o começo de sua coluna na CBN, eu teria que fazer um script que fosse alterando o valor do ano, do mes e do dia. Para isto, posso criar três laços, um dentro do outro:

Supondo que a coluna do Max Gehringer tenha começado em 2004, para baixar todos os podcasts até o ano de 2017, podemos fazer da seguinte forma:

#!/bin/bash

for A in {04..17}
do
  for M in {01..12}
  do
      for D in {01..31}
      do
         wget http://www.cbn.com.br/podcasts/max_$A$M$D.mp3
         sleep 10
      done
  done
done

O laço mais externo, que gera o ano, roda mais devagar, ou seja, para cada valor da variável $A, a variável $M varia de 1 a 12, e para cada valor de $M a variável $D varia de 1 a 31. São três relógios, o mais externo (o ano) roda mais devagar, o do meio (o mês) roda um pouco mais rápido e o mais interno (o dia) roda mais rápido do que todos os outros.

Infelizmente a CBN mudou o esquema de armazenamento de seus podcasts e este script não funciona mais, entretanto, continua sendo um bom exemplo de laços aninhados.

Para a CBN não pensar que está sofrendo um ataque de negação de serviço, podemos fazer com que o sistema execute uma pausa ao final de cada download. Para isto inserimos o comando sleep 10 para fazer com que entre um download e outro o sistema faça uma pausa de 10 segundos.

É claro que nem todos os arquivos que eu solicitar existirão, mas neste caso ocorrerá apenas uma mensagem de erro sobre a qual não preciso tomar nenhuma atitude. Este script também não trata meses com duração diferente de 31 dias, que também julguei não ser preciso. Todavia, existem situações em que um tratamento completo precisa ser inserido na Shell.

Como não podia deixar de ser, também dá para fazer tudo em uma linha:

echo {15..17}{01..12}{01..31} | \
	 tr ' ' '\n' | \
         xargs -i wget http://www.cbn.com.br/podcasts/max_{}.mp3

Faça um teste e veja como funciona bem. As mágicas do xargs serão explicadas em detalhe no curso de programação shell Linux que será oferecido em novembro.

Retornar ao sumário

2.2. Envio de mensagens para múltiplos destinatários

O envio de mensagens de email para múltiplos destinatários pode ser automatizado por meio de um programa em Shell muito simples.

O primeiro passo é montar o cabeçalho da mensagem, que deve conter, no mínimo, o nome do destinatário (To:), o nome do remetente (From:) e o assunto da mensagem (Subject:). Como exemplo, vamos utilizar uma versão resumida da primeira mensagem da Dicas-L, enviada no dia 3 de março de 1997 (já faz um tempinho, né?)

while read nome email
do
/usr/sbin/sendmail $email << EOF
From: Rubens Queiroz de Almeida <queiroz@dicas-l.com.br>
To: user@gmail.com
Subject: Receitas de uso do comando find

Colaboração: Rubens Queiroz de Almeida

Data de Publicação: 03 de Março de 1997

O comando find é extremamente poderoso e flexível
para descobrir arquivos que atendem a determinadas
especificações.

Por exemplo, suponhamos que queiramos descobrir todos os
arquivos que não possuem dono em nosso sistema. Esta
situação é extremamente comum, visto que usuários
são criados e apagados diariamente e ficam vagando
pelo sistema e podem eventualmente vir a comprometer a
segurança. O comando

find / -nouser -print

—-----------------------------------------------------------------------
As mensagens da Dicas-L são enviadas diariamente para
`wc -l lista.txt` assinantes.

EOF

done < lista.txt

Esta Shell usa um recurso muito útil, o Here Document. Com este recurso nós podemos fornecer ao programa um trecho de texto, que serão os dados entregues ao programa especificado, em nosso caso, o programa sendmail. O programa sendmail irá interpretar como entrada todos os caracteres que encontrar até que os caracteres EOF sejam encontrados na primeira posição da linha.

Observe também a seguinte frase:

As mensagens da Dicas-L são enviadas diariamente para
`wc -l lista.txt` assinantes.

Uma prática que tenho seguido há vários anos, é sempre inserir no rodapé das mensagens o número de assinantes da minha lista. No texto, este valor é inserido como um resultado do comando wc (word count), com a diretiva -l, que me fornece o número de linhas do arquivo que contém a lista dos assinantes.

Note que o comando é delimitado por aspas invertidas (que costumam chamar de crase, apesar de ser um acento grave e não uma crase), o que faz com que no texto seja inserido o resultado do comando wc -l, pois as crases, servem para dar prioridade de execução, por exemplo:

Arqs=ls 

Dessa forma eu estaria atribuindo o literal ls à variável $Arqs. Se eu quiser atribuir a saída do comando ls, eu deveria fazer:

Arqs=`ls` 

Como em qualquer interpretador as operações são feitas de cima para baixo e da esquerda para a direita, precisamos dizer ao Shell para ele priorizar o comando ls e isso se consegue de duas maneiras:

  1. Usando as crases como vimos;
  2. Usando construções do tipo $(CMDs) onde CMDs é a linha de comds que desejamos priorizar.

Eu poderia fazer algo semelhante com qualquer comando disponível no sistema. Nesta mesma linha, supondo que este seja um e-mail que eu mande diariamente, eu poderia substituir:

Data de Publicação: 03 de Março de 1997

Por:

 Data de Publicação: $(date "+%d de %B de %Y") 

No comando date, se houver um sinal de adição (+), tudo que for precedido por um porcento (%) é considerado um caractere de formatação de tempo, senão, será considerado um literal e:

%d Dia (01-31)
%B Mês por extenso
%Y Ano com século (4 algarismos)

Uma coisa muito pouco conhecida e que é provável que até você desconheça: 90% das vezes que usamos Here Document, prejudicamos a indentação porque o label utilizado (em nosso exemplo usei EOF) não pode ter espaços antes nem depois, mas o que é segredo de estado é que pode ter <TAB>s antes, desde que você use um hifen após os sinais de maior (<<-). Experimente fazer:

if ....
then
    ....
    cat <<- Fim
        No dia $(date)
        meu diretório tinha:
        $(ls -l)
    Fim
fi

IMPORTANTE: Antes da palavra Fim tem um <TAB>.

Retornar ao sumário

2.3. Verificar o funcionamento de servidores

Para verificar se todos os servidores sob a sua supervisão estão funcionando corretamente, podemos montar um pequeno script que, a partir de uma lista de máquinas, realize um teste de tempos em tempos:

Antes de escrever o script, precisamos conhecer os códigos gerados pelo comando ping em cada situação que encontrar:

Status Código
Sucesso 0
Sem resposta 1
Outros erros 2

Se o código for zero, está tudo bem, não precisamos nos preocupar, se for diferente de zero (1 ou 2), o administrador deverá ser alertado.

O nosso script deverá então verificar estes códigos. Mas como fazer isso? O comando if do Shell é bem mais poderoso que os seus congêneres de outras linguagens, que testam somente condições (e somente 7 condições, verifique). Aqui esta instrução testa se um comando, no caso o ping, foi bem sucedido (código de retorno zero), quando executará o bloco do then. Caso contrário (código de retorno diferente de zero) o bloco do else será executado.

Os códigos de retorno que citamos são apresentados pela variável $? e existem, com valores diferentes, para todos os comandos, mas, por convenção, sempre que um código de retorno ($?) é zero, significa que o comando correspondente foi bem sucedido.

Só para despreocupar vocês, já adianto que o Shell tem uma instrução específica para testar condições, que é o test, que testa mais de 30 condições distintas, inclusive em nível de arquivos.

Vamos então ao script:

$ cat check_server.sh
#!/bin/bash

# check_server.sh v1.0

while read servidor
do
  echo "Testando $servidor"
  if ping -q -c5 $servidor
  then
        echo $servidor OK
  else
        echo $servidor não está respondendo
/usr/lib/sendmail queiroz@dicas-l.com.br << EOF
Subject: **URGENTE**: Servidor $servidor não está respondendo
From: System Admin <admin@dicas-l.com.br>

O servidor $servidor não está respondendo. Por favor, verifique o que
está ocorrendo o mais rápido possível.

Atenciosamente,

System Administrator
EOF
  fi
done < lista_de_servidores.txt

Note que neste script inserimos uma estrutura de controle de laço (while) e uma estrutura de controle de decisão (if). O laço while faz a leitura de todas as linhas do arquivo lista_de_servidores.txt e envia um ping para cada um dos servidores contidos na lista. Já o if testa a execução do comando ping caso ele tenha sido bem sucedido e, de acordo com a resposta que obtiver, toma as providências necessárias para cada situação.

No comando ping eu pedi que fossem enviados cinco pacotes para o servidor sendo testado. Se eu enviasse apenas um pacote, eu poderia criar um alarme falso, pois apenas um pacote de teste é muito pouco. Eu indiquei também que quero que o comando ping se comporte de maneira mais silenciosa (diretiva -q).

O segredo reside no if testando o retorno do ping, o que me indica se o comando foi bem sucedido ou não. Caso ele tenha se dado mal, quando seu código de retorno ($?) será diferente de zero, o bloco de comandos do else será executado e o administrador do sistema deve ser alertado para tomar as devidas providências. Neste caso, além da mensagem de erro ecoada para a tela, é enviada uma mensagem para o administrador da máquina. O arquivo lista_de_servidores.txt contém a relação de todos os servidores a serem testados.

Importante, veja que na mensagem enviada ao administrador foi incluída a variável $servidor, obtida a partir da leitura do arquivo lista_de_servidores.txt. As mensagens configuradas a partir dos Here Documents podem ser configuradas de maneira bastante completa, inserindo substituição de variáveis e resultados de comandos, como já vimos.

Retornar ao sumário

2.4. Teste de funcionamento dos serviços

Outra possibilidade muito interessante, que pode ser facilmente implementada com Shell scripts, é verificar se todos os serviços (DNS, Apache, MySQL, etc) estão funcionando corretamente.

O princípio é o mesmo, insiro em um arquivo os nomes de todos os serviços que desejo monitorar e por meio do laço while e da estrutura de decisão if, faço uma busca na lista de processos pelo nome do serviço. Se eu não encontrar o serviço, é sinal de problema.

$ cat check_services.sh
#!/bin/bash

# check_services.sh v1.0

Servidor=www.dicas-l.com.br

while read Servico
do
  if  ps -ef | grep $Servico | grep -v grep 1>&2 > /dev/null
  then
        echo $Servico OK
  else
        echo $Servico está fora do ar
/usr/lib/sendmail queiroz@dicas-l.com.br << EOF
Subject: **URGENTE**: O servico $Servico está fora do ar
From: System Admin <admin@dicas-l.com.br>

O serviço $Servico do $servidor não está
respondendo. Por favor, verifique o que
está ocorrendo o mais rápido possível.

Atenciosamente,

System Administrator
EOF
  fi
done < lista_de_servicos.txt

Importante, note que na linha em que usamos o grep, nós removemos da listagem o próprio comando grep, pois este grep irá constar da lista de processos justamente com o nome do serviço que estamos pesquisando.

$ ps -ef | grep named
bind     19501     1  0 22:35 ?        00:00:00 /usr/sbin/named -f -u bind
queiroz  19839 16913  0 22:42 pts/0    00:00:00 grep named

Caso queiramos monitorar os serviços Apache, MySQL, DNS, postfix e ssh, inserimos no arquivo lista_de_servicos.txt as seguintes linhas:

apache2
mysqld
named
postfix
sshd

Este valores podem variar de um sistema para outro, então, por meio do comando ps -ef, veja qual a denominação correta para estes serviços em seu sistema.

Ao executarmos o comando check_services.sh será ecoado para a tela o resultado da verificação:

$ ./check_services.sh
apache2 OK
mysqld OK
named está fora do ar
postfix está fora do ar
sshd OK

Para todos os serviços que não estiverem operantes, será enviado um email de alerta para o administrador.

2.5. A diretiva case

A diretiva case não pode ser considerada um laço, visto que não executa repetidamente um bloco de código. Porém, como os laços, ele direcionam o fluxo do programa de acordo com as condições verificadas no início ou final do bloco.

A sintaxe da diretiva case é a seguinte:

case $variable in
     padrão-1)      
          comandos
          ;;
     padrão-2)      
          comandos
          ;;
     padrão-3|padrão-4|padrão-5)
          comandos
          ;; 
     padrão-N)
          comandos
          ;;
     *)
          comandos
          ;;
esac

Como não poderia deixar de ser, aqui vai mais um exemplo. No portal Dicas-L com bastante frequencia eu preciso editar um artigo para corrigir algum erro de conteúdo ou simplesmente para ajeitar a formatação. A seguir, apresento um trecho deste script, apenas para demonstrar o uso da estrutura case.

$ cat mktip.sh
#!/bin/bash

# O shell script recebe como argumento o nome
# do arquivo que se quer editar. Esta condição
# é verificada no início do script.

if [ $# -ne 1 ];
then
   echo Uso: ${0#*/} dica   # Apaga tudo à esquerda da barra (/)
   exit 1
fi

# A resposta fornecida é armazenada na
# variável $ans

# Repare que o S está sendo oferecido como default

read -n1 -p "Gostaria de editar esta dica? (S|n) : " ans  

ans=${ans:-S}   # Caso vazia (aceitou default), $ans recebe S

[ ${ans^} == S ] && vi $Dica.t2t
GeraDica

Se a resposta para a pergunta Gostaria de editar esta dica? (S|N) : for sim (s ou S), o arquivo será aberto no editor de textos vi e em seguida será invocada uma função chamada GeraDica (não mostrada aqui) que irá gerar o arquivo html. Se a resposta for não (n, N, ou qualquer outra resposta), será invocada apenas a função GeraDica.

Mais um exemplo:

$ cat tecla.sh
#!/bin/bash

# Identificando o caractere digitado

read -n1 -p "Pressione uma tecla: " tecla

case "$tecla" in
  [[:lower:]]   ) echo -e "\nLetra minúscula";;
  [[:upper:]]   ) echo -e "\nLetra maiúscula";;
  [0-9]         ) echo -e "\nNúmero";;
  *             ) echo -e "\nPontuação, espaço em branco ou outros";;
esac

Neste exemplo, o script executa uma única vez e encerra. Para fazer com que ele seja encerrado apenas quando eu digitar <CTRL>+C, posso colocar o código do case dentro de um laço while:

$ cat tecla.sh
#!/bin/bash

while true
do
# Identificando o caractere digitado
   echo; echo "Pressione uma tecla e em seguida pressione enter"
   read tecla
   
   case "$tecla" in
   [[:lower:]]   ) echo -e "\nLetra minúscula";;
   [[:upper:]]   ) echo -e "\nLetra maiúscula";;
   [0-9]         ) echo -e "\nNúmero";;
   *             ) echo -e "\nPontuação, espaço em branco ou outros";;
   esac
done

Referência: Advanced Bash-Scripting Guide.

O Bash 4.0 introduziu duas novas facilidades no comando case. A partir dessa versão, existem mais dois terminadores de bloco além do ;;, que são:

Exemplos

Suponha que no seu programa possam ocorrer quatro tipos de erro e você resolva representar cada um deles por uma potência de 2, isto é, 20=1, 21=2, 22=4, 23=8, de forma que a soma dos erros ocorridos gere um número único para representá-los (é assim que se formam os números binários). Assim, se ocorrem erros dos tipos 1 e 4, será passado 5 (4+1) para o programa e se os erros forem 1, 4 e 8, será passado 13 (8+4+1). Observe a tabela a seguir:

Soma Erros
8 4 2 1
8 x - - -
7 - x x x
6 - x x -
5 - x - x
4 - x - -
3 - - x x
2 - - x -
1 - - - x
0 - - - -
$ cat case.sh
#!/bin/bash
# Recebe um código formado pela soma de 4 tipos
#+ de erro e dá as msgs correspondentes. Assim,
#+ se houveram erros tipo 4 e 2, o script receberá 6
#+ Se os erros foram 1 e 2, será passado 3. Enfim
#+ os códigos de erro seguem uma formação binária.

Bin=$(bc <<< "obase=2; $1") Passa para binário
Zeros=0000
Len=${#Bin} Pega tamanho de $Bin
Bin=${Zeros:$Len}$Bin Preenche com zeros à esquerda
# Poderíamos fazer o mesmo que foi feito acima
#+ com um cmd printf, como veremos no capítulo 6
case $Bin in
1[01][01][01]) echo Erro tipo 8;;&
[01]1[01][01]) echo Erro tipo 4;;&
[01][01]1[01]) echo Erro tipo 2;;&
[01][01][01]1) echo Erro tipo 1;;&
0000) echo Não há erro;;&
*) echo Binário final: $Bin
esac

Repare que todas as opções serão testadas para saber quais são bits ligados (zero=desligado, um=ligado). No final aparece o binário gerado para que você possa comparar com o resultado. Testando:

$ case.sh 5
Erro tipo 4
Erro tipo 1
Binário final: 0101

$ case.sh 13
Erro tipo 8
Erro tipo 4
Erro tipo 1
Binário gerado: 1101

Obs.: Todas as listas [01] neste exemplo poderiam ser substituídas por um ponto de interrogação (?), já que ele é um metacaractere que representa qualquer caractere e o nosso programa só gerou zeros ou uns para cada uma dessas posições.

Retornar ao sumário

2.6. Extração de endereços de email de arquivos de log

Alguns anos atrás a Dicas-L foi expulsa de um provedor nos EUA sob a alegação de que eu estava fazendo spam. Segundo o provedor, eles tinham tolerância zero para spam e eu fui condenado sem direito a defesa.

Como eu não faço spam, todos os assinantes da Dicas-L assinam a lista mediante um mecanismo de confirmação dupla, preciso explicar o que aconteceu.

Com o tempo, muitos assinantes deixam de usar seus endereços eletrônicos fazendo com que os provedores os removam. Uma das técnicas utilizadas por provedores para detectar spam é inserir endereços de email não existentes em alguns locais para que os softwares que colhem emails pela web os capturem. Desta forma, se alguém envia mensagens para estes endereços propositalmente espalhados pela Internet, certamente é um spammer.

Bom, no meu caso não foi bem assim. Eu fui considerado um spammer por enviar mensagem para um endereço não existente mas que já havia existido um dia.

Felizmente, como eu mantenho múltiplos backups do meu conteúdo, eu consegui contratar um novo provedor e colocar o site novamente no ar em algumas horas.

Para evitar uma repetição deste fato, eu faço uma limpeza periódica da lista de assinantes, removendo os endereços que não existem mais. Como não poderia deixar de ser, eu uso um pequeno script bash para realizar esta tarefa:

$ cat clean_mail.sh
#!/bin/bash

assinantes=dicas-l.list
log=/var/log/mail.log
log_processado=/tmp/mail.log.$$
enderecos_invalidos=/tmp/enderecos_invalidos.$$
enderecos_validos=/tmp/enderecos_validos.$$

cd /var/log/

for log in mail.*
do
fgrep -i "unknown user" $log | \
      grep -o '[[:alnum:]+._-]*@[[:alnum:]+._-]*' | \
      sort | uniq -i >> $enderecos_invalidos
done

vi $enderecos_invalidos

grep -v -f $enderecos_invalidos $assinantes > $enderecos_validos

wc $enderecos_validos $assinantes

Eu utilizo o programa fgrep para buscar nos vários arquivos de log do sistema (mail.*) as palavras unknown user. Sempre que eu tento enviar mensagens para um endereço eletrônico e ele não mais existir no servidor remoto, estas palavras são registradas no log do sistema, juntamente com o endereço eletrônico inválido. O comando fgrep se diferencia do programa grep por ser muito mais rápido e por fazer a busca baseando-se apenas nos caracteres fornecidos na linha de comando, sem nenhum processamento adicional. Como os arquivos de log para um site como a Dicas-L, com milhares de assinantes, são enormes, a diferença na velocidade de processamento faz toda a diferença.

A diretiva -o do comando grep faz com que sejam impressos apenas os caracteres que batem com a expressão regular, e não a linha inteira. Desta forma, apenas o endereço de email é capturado.

A expressão regular

[[:alnum:]+._-]*@[[:alnum:]+._-]*

é a representação de um endereço eletrônico. O resultado é então ordenado (sort) e removidas as linhas duplicadas (uniq -i).

O comando wc é usado apenas para comparar a listagem original com a listagem processada, para ver quantos endereços serão removidos da lista de assinantes da Dicas-L.

A parte final, que é a atualização da lista de assinantes, eu faço manualmente, para evitar algum eventual problema ou catástrofe. É claro que eu tenho múltiplos backups para recuperação em caso de problemas, mas eu prefiro ser cuidadoso.

Vocês devem estar se perguntando como interpretar a expressão regular montada para identificar endereços de email no arquivo de log, mas esta explicação está fora do escopo destas aulas introdutórias. Uma explicação bastante aprofundada e detalhada faz parte do curso de programação Shell Linux criado pelo Júlio Neves, e que será lançado no início de novembro de 2017.

Retornar ao sumário

2.7. Configuração automática do firewall

Algum tempo atrás eu notei um aumento elevado de acesso aos meus sites a partir de computadores oriundos da China. E nos últimos dias, conforme apontado pelo software OSSEC praticamente metade de todos os acessos aos sites se constituiam em tentativas de invasão.

Em vista disto, resolvi tomar uma atitude mais radical, bloqueando todos os acessos a partir das redes da China. Ao tentar localizar os números das redes chinesas, constatei que o problema está se agravando e é muito mais comum do que eu imaginava.

Criei então um pequeno script para criar as regras de firewall proibindo acessos a partir das redes chinesas.

Este script, reproduzido a seguir, é carregado durante o boot da máquina.

$ cat blockchina.sh
#!/bin/bash
cd /root/
BLOCKDB=/root/chinese-iptables-blocklist.txt

if [ -f $BLOCKDB ]
then
 while read IPS
   do
     iptables -A INPUT  -s $IPS -j DROP
     iptables -A OUTPUT -d $IPS -j DROP
   done < $BLOCKDB
else
   echo "Arquivo $BLOCKDB não existe"
   exit 1
fi

A primeira coisa a ser feita é verificar se existe o arquivo contendo a lista de endereços IP da rede chinesa. Se existir o arquivo, passo então a bloquear os endereços, um a um.

ALERTA IMPORTANTE: como qualquer medida que faz o bloqueio de acesso, tome cuidado para não se excluir, perdendo o contato com o servidor e ficando impossibilitado de realizar os ajustes que se fizerem necessários. Para variar, isto aconteceu comigo. Da mesma forma, caso se decida a seguir os procedimentos aqui descritos, a responsabilidade é inteiramente sua, não ofereço nenhum tipo de garantia quanto ao funcionamento deste roteiro. Faça testes exaustivos antes de colocar em produção.

Retornar ao sumário

2.8. Conclusão

Se você chegou até aqui, parabéns! Eu tenho certeza de que você está maravilhado com o poder e simplicidade da programação shell Linux.

Você pode agora observar que a maioria dos scripts utilizados neste tutorial seguem uma mesma lógica, o que varia são os comandos e a função de cada um deles. Utilizei apenas exemplos práticos e úteis. Resumidamente, recheando com os comandos corretos um trecho de código com quatro linhas descrevendo um laço while ou for, e testando algumas condições com o comando if, podemos executar tarefas bastante complexas.

Este texto não é uma demonstração exaustiva de todas as possibilidades, isto é feito no curso de programação Shell que lançaremos em novembro, mas apenas com o que foi abordado aqui você pode aumentar em muito a sua produtividade e fazer bonito com o seu chefe e os seus colegas de trabalho. Mas continue estudando, embora você tenha avançado muito, ainda tem muita coisa boa pela frente.

Se gostou ou não, se tiver comentários, sugestões, enfim, qualquer coisa que deseje nos transmitir, por favor, deixe sua mensagem abaixo. Sua opinião vale muito para nós e irá nos ajudar a melhorar este material.

O campo de comentário usa um plugin do Facebook. Caso você não tenha conta no Facebook, envie seu comentário para o endereço programacao_shell@dicas-l.com.br, que sua mensagem será encaminhada para mim (Rubens Queiroz) e para o Júlio Neves. Tenha certeza de que todas as mensagens serão respondidas por nós.

Não se esqueça, a terceira e última parte será enviada no dia 6 de novembro. Até lá!

Júlio Cezar Neves Rubens Queiroz de Almeida
Retornar ao sumário

Privacy Policy | Política de Privacidade