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

Workshop Programação Shell Linux - Parte 1

Sumário

1.1. Introdução

1.2. Sistemas Unix e derivados (*nix)

1.3. O que é um shell script?

1.4. Você sabe o que está fazendo?

1.5. Faça apenas uma coisa, e bem feita

1.6. Material Complementar - Palestra: A Filosofia do Unix

1.7. Referências

1. Workshop Programação Shell Linux - Parte 1

1.1. Introdução

Esta pequena introdução à Programação Shell Linux tem um enfoque um tanto diferente de cursos introdutórios. O nosso objetivo é demonstrar o grande valor que o conhecimento da programação Shell possui para o desenvolvimento da carreira e também para conseguir resultados fantásticos em pouco tempo, com apenas algumas linhas de programação.

Os conceitos serão demonstrados por meio de exemplos, sempre dentro de um contexto de aplicação. É claro que estaremos apenas arranhando a superfície dos imensos recursos oferecidos pela programação Shell, mas esperamos que esta breve introdução ilustre o imenso potencial deste ambiente.

Este texto foi formatado para ser lido em qualquer tipo de dispositivo, (celulares, tablets ou computadores) mas o ideal é que você tenha acesso a um sistema com GNU/Linux instalado para poder praticar os comandos e estruturas abordados. Importante, tente ler este tutorial diversas vezes, sempre obtemos uma nova perspectiva e um conhecimento mais aprofundado sempre que revisamos os conceitos aprendidos.

Antes de prosseguirmos, eu gostaria de pedir um pequeno favor. Não deixe de nos enviar seus comentários, para que possamos melhorar onde erramos ou não fomos claros. Nenhum trabalho é perfeito e contamos com seus comentários para melhorar onde erramos e aperfeiçoar ainda mais este trabalho. Elogios também são bem-vindos, é claro.

Retornar ao sumário

1.2. Sistemas Unix e derivados (*nix)

Ken Thompson e Denis Ritchie, criadores do Unix Quando se fala em programação Shell, não devemos vê-la como apenas mais uma linguagem de programação. Ela se insere dentro de um contexto mais amplo representado por sistemas *nix (Linux, FreeBSD, OpenBSD, AIX, SunOS, Solaris, HPUX, e muitos outros). Sistemas *nix possuem uma filosofia de simplicidade, que explicam a sua enorme popularidade nos dias atuais. Praticamente toda a infraestrutura da Internet depende de sistemas *nix. São servidores web, dns, correio eletrônico, roteadores, sem deixar de mencionar a imensa infraestrutura computacional de gigantes como Google e Facebook, o seu telefone Android, e muito mais. Isto tudo não é por acaso, mas um sinal da qualidade e robustez destes sistemas operacionais. Esperamos que esta introdução à programação Shell Linux também lhe conquiste e lhe apresente um mundo de grandes possibilidades.

O grande poder das Shell scripts é a combinação de centenas de comandos em conjunto com estruturas de laço e decisão muito poderosas. O aprendizado é rápido, conhecendo alguns poucos comandos você já consegue criar uma Shell e ir sofisticando com o tempo e estudo. Adaptando às suas necessidades os exemplos que usamos neste workshop você poderá aumentar em muito a sua produtividade.

O nosso objetivo neste workshop é lhe fornecer os conhecimentos iniciais para que possa realizar tarefas surpreendentes. O curso de programação Shell Linux, de autoria do Júlio Cezar Neves, o Papai do Shell, oferece um enfoque mais aprofundado e detalhado de tudo o que abordaremos neste workshop, indo desde o muito básico, como declaração de variáveis, até o muito avançado, como named pipes e paralelismo de processos, passando por uma imersão total em expressões regulares, que também serão muito úteis em outras linguagens e editores. Tudo isto em cinco semanas de estudo.

Retornar ao sumário

1.3. O que é um shell script?

A interação do usuário com sistemas *nix se dá por meio de centenas de comandos. A tabela abaixo relaciona alguns destes comandos, os mais usados, e suas funções.

Comando função
awk linguagem de processamento de texto
cd usado para navegar na estrutura de diretórios
crontab, cron automatização de tarefas
df verifica a taxa de ocupação de espaço em disco
diff encontra a diferença entre arquivos
find busca por arquivos que atendam a uma determinada característica
grep imprime as linhas de um arquivo que casem com um argumento de busca
ls listagem de informações sobre arquivos
ps listagem de processos
sed string editor, modifica o conteúdo de um arquivo ou texto
ssh login remoto, com criptografia
tar tape archive, usado para fazer backups
vim o maior e melhor editor de textos de sistemas *nix

Esta é uma listagem muito pequena, inserida aqui apenas para explicar um conceito. Existem centenas de outros comandos, sem contar aqueles que você pode instalar a partir de repositórios isolados. Tenha certeza, se você precisa, alguém já criou um comando que faz o que você precisa. Esta é a beleza dos sistemas livres e abertos.

Vou te dar uns exemplos de instruções que você nunca imaginou existirem, mas como eu disse, já estão lá prontas esperando que alguém que conheça a programação Shell as use. Copie os comandos abaixo e cole-os no seu terminal:

mkdir ShellEAD       # Cria um diretório
cd ShellEAD          # Já que criei, pulo dentro
alias rm=rm\ -i      # A partir de agora, sempre pedirá confirmação para remover
eval \>ARQ{01..10}\; # Cria desde ARQ01 até ARQ10, 100 vezes mais rápido que o touch
ls                   # Viu!? Eu não disse?

Nesse momento, se quisermos remover os 10 arquivos criados e fizesse:

 rm * 

Eu teria de responder às 10 perguntas que o rm faria pedindo confirmação. Mas como eu sou um cara preguiçoso, prefiro fazer assim:

 yes | rm * 

Desta forma o comando rm vai pedir confirmação e o yes a dará automaticamente. Experimente!

Um outro comando interessante e meio desconhecido é o columns que divide a tela em colunas, obedecendo aos parâmetros que você passou. Só para te demonstrar, por enquanto vou explicar somente duas opções básicas do comando:

Como eu sempre quero ter a minha tela toda disponível para colunar as minhas saídas, eu crio um artifício: tenho no meu .bashrc uma linha que diz:

 alias columns='columns -W $(tput cols)' 

Vamos então continuar no diretório de rascunho ShellEAD que criamos e, com a tela toda aberta vamos fazer:

eval \>ARQ{1..100}\;         # Cria desde ARQ1 até ARQ100, 100 vezes mais rápido que o touch
ls                           # Só para o ls colunar do jeito dele
ls | columns -W $(tput cols) # Usou toda a largura da tela
ls | columns -c 15           # Lista o ls em 15 colunas
ls | columns -c 10           # Lista o ls em 10 colunas

Como você pode ver e só quis te mostrar isso, no Shell existe comando para tudo (desconhecidos pela maioria) e esse é um dos motivos pelo qual os scripts são tão pequenos.

Bem, para limpar a bagunça, faça:

$ cd ..
$ yes | rm -r ShellEAD

Continuando, os comandos que você digita na linha de comandos, podem ser incluídos em um arquivo que passa então a ser um Shell script.

Vejamos um pequeno exemplo, que é uma atividade muito importante do dia a dia de um administrador de sistemas, monitorar a taxa de ocupação de espaço em disco. Como podemos ver pela tabela acima, esta tarefa é feita com o comando df:

#!/bin/bash
email=queiroz@dicas-l.com.br

/usr/lib/sendmail $email << EOF
Taxa de ocupação de espaço em disco
`df`
EOF

Este script enviará para o meu endereço eletrônico a seguinte mensagem:

Date: Sun, 22 Oct 2017 11:00:23 -0200 (BRST)
From: Rubens Queiroz de Almeida 

Taxa de ocupação de espaço em disco
Filesystem        1K-blocks      Used Available Use% Mounted on
/dev/ploop27265p1 309504000 231912336  64904484  79% /
devtmpfs            6291456         0   6291456   0% /dev
tmpfs               6291456         0   6291456   0% /dev/shm
tmpfs               6291456    600820   5690636  10% /run
tmpfs                  5120         0      5120   0% /run/lock
tmpfs               6291456         0   6291456   0% /sys/fs/cgroup
none                6291456         0   6291456   0% /run/shm

Basta agora automatizar a execução, por meio do cron de um scriptizinho que analisará a saída do df e, caso haja algum file system "entupido", me mandará este e-mail como um alerta. É assim que procedem os bons admins.

IMPORTANTE: este é apenas um exemplo didático para mostrar o que é um Shell script. Existem dezenas de formas de se fazer algo semelhante, gerando o email apenas quando um determinado patamar de ocupação for atingido, podendo até mesmo tomar algumas medidas automáticas como remover arquivos de diretórios temporários que não tenham sido acessados há alguns dias, etc. Veremos alguns exemplos mais sofisticados nos próximos dias.

Então, vamos em frente.

Retornar ao sumário

1.4. Você sabe o que está fazendo?

Um dos princípios filosóficos adotado pelos criadores do Unix é: o usuário sabe o que está fazendo. Parece bastante radical, e o mais importante a ser destacado, é que os criadores do Unix projetaram um sistema para pessoas que possuissem uma cultura computacional e que soubessem o que estavam fazendo. O sistema nunca pede confirmação para nenhuma ação, a menos que seja instruído a fazer isto. Por exemplo, o comando que apaga arquivos se chama rm (remove). Se você emitir o comando abaixo, a partir do diretório raiz, como o superusuário, conhecido como root, você irá apagar todos os arquivos do sistema:

# rm -rf * 

Acredite, eu já fiz isto. Só desconfiei que algo estava errado pois o comando estava demorando muito a finalizar, o que não é de se espantar, pois tudo estava sendo apagado. Infelizmente, quando cancelei já era tarde demais, nada funcionava. Bom, mas para isto que servem os backups, não? Mas eu não tinha backup ...

Nos sistemas mais modernos, como o GNU/Linux, este comando é representado por um alias (ou apelido):

alias rm="rm -i" 

A diretiva "-i" sinaliza ao programa rm que ele deve funcionar no modo interativo, ou seja, perguntando ao usuário a cada vez que precisar tomar uma ação. Eu acho isto errado, mas enfim ...

Outra coisa interessante, todos que começam a trabalhar com sistemas *nix, inevitavelmente, emitirão um comando do tipo:

$ cat 

O comando cat exibe na tela o conteúdo de um arquivo. Se você não fornecer o nome do arquivo, o sistema ficará parado, esperando a entrada (o arquivo) para que possa executar alguma coisa. Nenhuma mensagem de erro, absolutamente nada. E o sistema fica lá, parado e não te diz o que está errado. Afinal de contas, você sabe o que está fazendo, não? E isto faz todo o sentido, se o programa emitisse uma mensagem de erro, esta mensagem precisaria ser tratada de alguma forma (removida, alterada, etc). Como em sistemas *nix todos os programas são filtros, em que o conteúdo de um comando pode ser passado para um outro, este tratamento iria adicionar um nível adicional de complicação, sem necessidade alguma.

Retornar ao sumário

1.5. Faça apenas uma coisa, e bem feita

Das muitas características de sistemas Unix que até hoje me maravilham, a principal delas é o princípio de que cada programa deve desempenhar uma, e apenas uma tarefa, e que cada programa possa se comunicar com outros. Ao invés de ter programas enormes, que fazem uma grande quantidade de tarefas, sistemas Unix privilegiam programas pequenos que podem ser facilmente combinados com outros programas. Um programa grande e complexo é difícil de se manter e sua utilização é bastante limitada. Programas pequenos, por outro lado, são mais fáceis de criar, entender e manter.

Vejamos um exemplo simples. Uma boa prática para fazer com que o seu site melhore seu posicionamento nos mecanismos de busca, é reproduzir, na URL, o título do documento. Isto pode ser feito da seguinte maneira:

% Titulo="A Filosofia do Linux"
% url=`echo $Titulo | sed "s/ /_/g" | tr [:upper:] [:lower:]`
% echo $url
a_filosofia_do_linux

Primeiramente, eu defini a variável Titulo, com o texto A Filosofia do Linux. Para usar este título na url do meu documento, é recomendável retirar os espaços em branco e converter as letras maiúsculas em minúsculas.

Para fazer isto, eu usei três programas: echo, sed e tr, cada um deles comunicando-se por meio de um pipe, representado pelo caractere |. Pipe, em inglês, significa cano. Ou seja, existe uma tubulação ligando um comando ao outro. O que sai pela tubulação de um comando entra pela tubulação do outro, até o resultado final.

Pipes

O comando echo ecoa para a tela o valor da variável Titulo, que por sua vez é transmitido para o programa sed (stream editor), que transforma os espaços em branco no caractere _. Finalmente, o comando tr traduz todas as letras maiúsculas em seu equivalente em letras minúsculas, por meio das diretivas [:upper:] [:lower:]. [:upper] representa os caracteres em caixa alta e [:lower:] representa os caracteres em caixa baixa. O comando tr aceita duas diretivas, o que é ([:upper:]) e como deve ficar ([:lower:]).

Em um sistema operacional menos inteligente, eu teria que escrever um programa que pega um texto, substitui os espaços em branco em underline e transforma as letras maiúsculas em minúsculas. O problema é que este programa faria apenas isto, mais nada. Já em sistemas Unix, os comandos echo, sed e tr podem ser usados para em uma infinidade de situações. Isto é fantástico!

Esta é uma maneira de se fazer isto, entretanto, uma forma ainda mais enxuta de se fazer a mesma coisa, e usando apenas um programa, seria:

% Titulo="A Filosofia do Linux"
% url=$(tr '[:upper:] ' '[:lower:]_' <<< $Titulo)
% echo $url
a_filosofia_do_linux

Esta é a beleza da coisa, sempre existem vários caminhos, não se preocupe se a sua primeira solução não for a melhor, a maestria requer um investimento em tempo e estudo. Em virtude disso costumo dizer: "Nunca pergunte se dá para fazer algo em Shell. A resposta é sempre: dá! A pergunta correta é: qual é a melhor maneira de se fazer esse algo em Shell, pois sempre existem diversas formas diferentes de fazer a mesma tarefa."

Essa sequência de pipes também poderia poderia ser feita somente com sed:

url=$(sed -r "s/ /_/g; s/(.*)/\L\1/" <<< $Titulo)

Vejamos mais um exemplo. O comando find localiza arquivos atendendo a um determinado critério. Mas ele apenas localiza, não toma nenhuma ação sobre os arquivos que encontra. Isto pode ser feito por um outro programa, chamado xargs. Vamos então localizar todos os arquivos a partir de um certo ponto na árvore de diretórios e em seguida contar o número de linhas de cada um destes arquivos:

 find / -type f -exec wc -l {} \; 

Através da diretiva -exec é invocado o programa wc que fará a contagem das linhas para cada um dos arquivos encontrados. O que poucos sabem é que se trocarmos esta dupla \; que finaliza a linha por um sinal de mais (+), a execução do comando será muito mais veloz. Para você ter uma ideia, aqui vai um find que pesquisa a partir do meu diretório home (~) todos os arquivos que usam o comando date e os conta, informando o total. O find foi formatado das duas maneiras e o comando time mede os tempos gastos em cada um dos casos. A diferença é gritante!...

$ time find ~ -type f -exec grep -l date {} \; | wc -l
605
real	0m1.294s
user	0m0.036s
sys	0m0.112s

$ time find ~ -type f -exec grep -l date {} + | wc -l
605
real	0m0.017s
user	0m0.008s
sys	0m0.004s

Se você quer executar um número de comandos em cada arquivo que encontrar, existe um meio de fazê-lo também - e isso não requer a utilização da opção -exec. Ao contrário, redirecione (com pipe) a saída do comando find normal para um laço while como este e você pode executar quantos comandos desejar entre os marcadores do e done:

find . -type f | while read i
do
	file $i
	ls -l $i
	wc -l $i
done

Veja que interessante, o comando find localiza os arquivos, que por sua vez são entregues ao laço while e, dentro dele, invocamos três outros comandos: file, que identificará o tipo de arquivo localizado, ls, que nos dá diversas informações sobre o arquivo, como o tamanho, proprietário, etc. e o comando wc, que contará o número de linhas do arquivo.

Vejamos agora um exemplo um pouco mais complexo:

 who | cut -f1 -d' ' | sort | uniq | paste -sd,

O comando who exibe uma lista dos usuários logados no sistema. Este resultado é passado através de um pipe (o caractere |) para o segundo comando na sequência, o comando cut, que pegará apenas o primeiro componente da saída gerada pelo comando who, neste caso, o nome do usuário. Em seguida, este resultado é ordenado (sort), são removidas as linhas repetidas (uniq) e finalmente, o comando paste separará os nomes dos usuários com vírgulas. A diretiva -s do comando paste combina o resultado que recebe em uma única linha e a diretiva -d, indica que os campos devem ser separados por vírgulas. Esta é uma maneira poderosa de se demonstrar como podemos combinar diversos pequenos programas, que fazem apenas uma coisa, para obtermos o resultado desejado.

O que segue é uma história real, acontecida muitos anos atrás. Em uma sexta-feira, no final da tarde, eu atendi um analista de desenvolvimento que estava planejando passar o final de semana alterando centenas de arquivos de um sistema corporativo. Um trabalho insano, que consumiria dezenas de horas de trabalho. Perguntando um pouco mais, para saber exatamente o que seria feito, vi que um simples laço de programação com o comando sed daria conta de toda a tarefa em menos de um minuto. Fiz um backup dos arquivos originais (nunca esqueça do backup!) e, com a permissão do analista, rodei o script em Shell (4 linhas) e fiz em segundos o trabalho que ele havia planejado para todo um final de semana. Não me recordo exatamente o que foi feito, mas era algo simples, simplesmente a troca de algumas palavras por outras. O trecho de código abaixo dá uma boa ideia do que foi executado:

for f in *.c
do
    sed -i "s/Hello World/Hello Brazil/" $f
done

A variável $f irá assumir como valores todos os arquivos do diretório corrente que possuam a extensão .c. Para cada um destes arquivos será executado o comando "sed -i "s/Hello World/Hello Brazil/" $f. Em todos os arquivos selecionados a frase Hello World será substituída pela frase Hello Brazil. A diretiva -i do comando sed fará com as alterações sejam gravadas diretamente no arquivo original (afinal de contas, eu fiz o backup, né?)

Veja mais uma novidade, o próprio comando sed pode providenciar o backup para nós:

for f in *.c
do
    sed -i.veio "s/Hello World/Hello Brazil/" $f
done

Tudo que é colocado após a diretiva -i é tratado como uma extensão sob a qual todos os arquivos originais serão salvos. Desta forma, um arquivo chamado prg.c, será salvo, antes da execução do sed, como prg.c.veio. Depois, se tudo der certo, você pode apagar os arquivos criados (rm *.c.veio).

O meu amigo nem acreditou que pudesse ser tão fácil fazer uma mudança deste porte em todos os arquivos de código do sistema inteiro, em apenas alguns segundos. Acho que foi por isto que ele pediu para trocar de área e trabalhar com Unix.

Mas a verdade é esta: com Shell script você pode fazer tudo, mais rápido, melhor e em menos tempo. Quem conseguir dominar a programação Shell tem tudo para se tornar uma espécie de mágico do mundo nerd.

Este exemplo é bastante simples, mas com um bom conhecimento de expressões regulares, podemos sofisticar bastante este script. Podemos determinar em quais linhas o comando será aplicado, podemos encadear diversos comandos em uma mesma linha, escolher em qual posição da linha o argumento de busca deve estar para que possamos agir sobre ele, e muito mais. Para você ter uma ideia do poder do comando sed, a editora O'Reilly publicou, já há vários anos, um livro que trata exclusivamente dos comandos sed e awk.

Vejamos mais um exemplo do uso do comando sed, que utilizo com frequencia. Eu mantenho um portal chamado Aprendendo Inglês, em que publico todos os dias uma mensagem engraçada ou uma citação em inglês, com o objetivo de ir construindo lentamente o vocabulário das pessoas. Reproduzo abaixo um exemplo do texto do portal:

=Fantastic exercise to lose weight=

Fantastic exercise that really helps you to lose weight:
Turn your head to the left. Good. Turn your head to the
right. Very good. Repeat this exercise whenever you are
offered any food.

==Vocabulary Help==

- help - ajudar
- lose weight - perder peso
- turn your head to the left - vire sua cabeça para a esquerda
- whenever you are offered food - sempre que te oferecerem comida 


»» [Ouça também o áudio deste texto http://www.aprendendoingles.com.br/mp3/2017018.mp3 ««]

O arquivo é dividido em duas partes, o texto em si e uma ajuda de vocabulário, com as palavras que julgo mais difíceis.

A gravação do áudio do texto é feita por um amigo nativo da Inglaterra. Para ele a ajuda de vocabulário (Vocabulary Help), não é necessária, serve apenas para gastar papel. Por esta razão, eu envio para ele apenas o texto, apagando todas as linhas a partir da linha que contém as palavras Vocabulary Help. Para montar o documento, uso o seguinte script:

for f in *.t2t
do
   sed "/Vocabulary Help/,$ d" $f >> ebook.txt
done

Novamente, quatro linhas. O argumento fornecido ao comando sed, "/Vocabulary Help/,$ d", indica uma faixa de linhas que começa ao serem encontradas as palavras Vocabulary Help e vai até o final do arquivo (caractere $). A ação a ser executada sobre todas as linhas que se enquadrarem neste critério é a deleção (caractere d).

A saída do comando sed é direcionada para o arquivo ebook.txt. Note que usamos o sinal >>, o que faz com que a saída gerada seja adicionada ao final do arquivo. Um erro muito comum é usar o sinal >. Neste caso, você terá que fazer tudo de novo, pois o conteúdo do arquivo ebook.txt será apenas a saída gerada pela última execução do comando sed.

Dois dos livros que escrevi, Linux: Dicas e Truques e As Palavras Mais Comuns da Língua Inglesa foram formatados com Shell scripts e integralmente desenvolvidos com software livre.

Para descobrir quais são as palavras mais comuns da língua inglesa eu usei um Shell script. De posse da relação de palavras, ordenadas por ordem de frequência, passei então à formatação. Este trabalho envolveu marcar algumas palavras em negrito, inserir numeração nas palavras, inserir a marcação do programa LaTeX e muito mais. Calculo que os recursos do Shell me economizaram várias semanas de trabalho.

As possibilidades são infinitas, podemos fazer praticamente tudo. É claro que antes precisamos conhecer os comandos disponíveis no sistema e suas funções. Para obter este conhecimento, vale a pena examinar o conteúdo dos diretórios /usr/bin, /usr/sbin e /sbin para ver o que temos disponível no sistema. Lembre-se, você não está restrito ao conteúdo destes diretórios, sistemas Debian GNU/Linux e derivados possuem mais de 10.000 pacotes e certamente um deles poderá atender à sua necessidade.

Em seguida, na lição do dia 3 de novembro, exploraremos, com muitos exemplos, as estruturas de laço e decisão disponíveis em sistemas GNU/Linux. Fique conosco, ainda temos muitas coisas interessantes para você.

Retornar ao sumário

1.6. Material Complementar - Palestra: A Filosofia do Unix

Se você dispuser de aproximadamente uma hora, gostaria de lhe convidar para assistir a palestra A Filosofia do Unix. Esta palestra foi apresentada originalmente na primeira turma do curso de programação shell distância da Unicamp. Apresenta uma breve história do Unix e em seguida aborda os princípios filosóficos sobre os quais se estruturou o sistema Unix. A palestra é baseada no livro The Unix Philosophy, de autoria de Mike Gankarz.

Retornar ao sumário

1.7. Referências

Vamos agora repetir o pedido que lhe fizemos no começo deste texto. Se gostou deste material ou se tiver algum comentário, sugestão, 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 e explorar novos temas.

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.

Até a próxima e muito obrigado!

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

Privacy Policy | Política de Privacidade