1.2. Sistemas Unix e derivados (*nix)
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
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.
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.
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:
-W
, na qual você diz quantas colunas tem a sua tela e o default é 79;
-c
, você especifica quantas colunas criar na saída.
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 AlmeidaTaxa 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.
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.
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.
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ê.
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.
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 |