- Garçom! Traz um “chops” e dois “pastel”. O meu amigo hoje não vai beber por que ele finalmente está sendo apresentado a um verdadeiro sistema operacional e ainda tem muita coisa a aprender!
- E então, amigo, tá entendendo tudo que te expliquei até agora?
- Entendendo eu tô, mas não vi nada prático nisso…
- Calma rapaz, o que te falei até agora, serve como base ao que há de vir daqui pra frente. Vamos usar estas ferramentas que vimos para montar programas estruturados, que o Shell permite. Você verá porque até na TV já teve programa chamado “O Shell é o Limite”.
- Para começar vamos falar dos comandos da família grep.
- grep? Não conheço nenhum termo em inglês com este nome…
- É claro, grep é um acrônimo Global Regular Expression Print, que usa expressões regulares para pesquisar a ocorrência de cadeias de caracteres na entrada definida (se bem que há uma lenda sobre como este comando foi nomeado: no editor de textos “ed”, o avô do “vim”, o comando usado para buscas era g/_expressao regular_/p, ou no inglês g/_re_/p.). Por falar em expressões regulares (ou regexp), o Aurélio Marinho Jargas tem todas as dicas em sua página (inclusive tutorias) que abordam o tema. Se você está mesmo a fim de aprender a programar em Shell, Perl, Python, … Acho bom você ler estes artigos para te ajudar no que está para vir.
Eu fico com o grep, você com a gripe
Esse negócio de gripe é brincadeira! É só um pretexto para pedir umas caipirinhas. Mas voltando à vaca fria, eu te falei que o grep procura cadeia de caracteres dentro de uma entrada definida, mas o que vem a ser uma “entrada definida”? Bem, existem várias formas de definir a entrada do comando grep. Vejamos: Pesquisando em um arquivo:
Pesquisando em vários arquivos:
Pesquisando na saida de comando:
No 1º exemplo, o mais simples, procurei a palavra rafael em qualquer lugar do arquivo /etc/passwd. Se quisesse procurá-la como um login name, isto é, somente no início dos registros deste arquivo, eu deveria fazer:
E para que serve este circunflexo e os apóstrofos, você vai me perguntar. O circunflexo (^), se você tivesse lido os artigos anteriores sobre expressões regulares que te falei, saberia que servem para limitar a pesquisa ao início de cada linha, e os apóstrofos (') servem para o Shell não interpretar este circunflexo, deixando-o passar incólume para o comando grep.
Olha que legal! O grep aceita como entrada, a saída de outro comando redirecionado por um pipe (isto é muito comum em Shell e é um tremendo acelerador de execução de comando já que atua como se a saída de um programa fosse guardada em disco e o segundo programa lesse este arquivo gerado), desta forma, no 3º exemplo, o comando who listou as pessoas “logadas” na mesma máquina que você (não se esqueça jamais: o Linux é multiusuário) e o grep foi usado para verificar se o Pelegrino estava trabalhando ou “coçando”.
A família grep
Este comando grep é muito conhecido, pois é usado com muita freqüência, o que muitas pessoas desconhecem é que existem três comandos na família grep, que são:
-
grep -
egrep -
fgrep
A principais características diferenciais entre os 3 são:
- O
greppode ou não usar expressões regulares simples, porém no caso de não usá-las, ofgrepé melhor, por ser mais rápido; - O
egrep(”e” de extended, extendido) é muito poderoso no uso de expressões regulares. Por ser o mais lento da família, só deve ser usado quando for necessária a elaboração de uma expressão regular não aceita pelogrep; - O
fgrep(”f” de fast, rápido, ou de “file”, arquivo) como o nome diz é o rapidinho da família, executa o serviço de forma muito veloz (por vezes é cerca de 30% mais veloz que o grep e 50% mais que o egrep), porém não permite o uso de expressões regulares na pesquisa.
Tudo que foi dito acima sobre velocidade, só se aplica à família de comandos grep do Unix. No Linux o grep é sempre mais veloz, já que os outros dois (fgrep e egrep) são scripts em Shell que chamam o primeiro e, já vou adiantando, não gosto nem um pouquinho desta solução.- Agora que você já conhece as diferenças entre os membros da família, me diga: o que você acha dos três exemplos que eu dei antes das explicações?
- Eu achei que o fgrep resolveria o teu problema de forma mais veloz do que o grep.
- Perfeito! Tô vendo que você está atento! Está entendendo tudo que estou te explicando! Então vamos ver mais exemplos para clarear de vez as diferenças de uso dos membros da família.
Exemplos
Eu sei que em um arquivo existe um texto falando sobre Linux só não tenho certeza se está escrito com L maiúsculo ou l minúsculo. Posso fazer de duas formas:
ou
No primeiro caso, a expressão regular complexa "(Linux | linux)" usa os parênteses para agrupar as opções e a barra vertical (|) como um “ou” lógico, isto é, estou procurando Linux ou linux.
No segundo, a expressão regular [Ll]inux significa: começado por L ou l seguido de inux. Por esta expressão ser mais simples, o grep consegue resolvê-la, portanto acho melhor usar a segunda forma, já que o egrep tornaria a pesquisa mais lenta.
Outro exemplo. Para listar todos os subdiretórios do diretório corrente, basta:
drwxr-xr-x 3 root root 4096 Dec 18 2000 doc
drwxr-xr-x 11 root root 4096 Jul 13 18:58 freeciv
drwxr-xr-x 3 root root 4096 Oct 17 2000 gimp
drwxr-xr-x 3 root root 4096 Aug 8 2000 gnome
drwxr-xr-x 2 root root 4096 Aug 8 2000 idl
drwxrwxr-x 14 root root 4096 Jul 13 18:58 locale
drwxrwxr-x 12 root root 4096 Jan 14 2000 lyx
drwxrwxr-x 3 root root 4096 Jan 17 2000 pixmaps
drwxr-xr-x 3 root root 4096 Jul 2 20:30 scribus
drwxrwxr-x 3 root root 4096 Jan 17 2000 sounds
drwxr-xr-x 3 root root 4096 Dec 18 2000 xine
No exemplo que acabamos de ver, o circunflexo (^) serviu para limitar a pesquisa à primeira posição da saída do ls longo. Os apóstrofos foram colocados para o Shell não “ver” o circunflexo (^).
Vamos ver mais um. Sabemos que as quatro primeiras posições possíveis de um ls -l de um arquivo comum (arquivo comum! Não é diretório, nem link, nem …) devem ser:
| Posição | 1ª | 2ª | 3ª | 4ª |
|---|---|---|---|---|
| Valores Possíveis | - |
r |
w |
x |
- |
- |
s (suid) |
||
- |
Assim sendo, para descobrir todos os arquivos executáveis em um determinado diretório eu deveria fazer:
-rwxr-xr-x 1 root root 2875 Jun 18 19:38 rc
-rwxr-xr-x 1 root root 857 Aug 9 22:03 rc.local
-rwxr-xr-x 1 root root 18453 Jul 6 17:28 rc.sysinit
Onde novamente usamos o circunflexo (^) para limitar a pesquisa ao início de cada linha, então as linhas listadas serão as que começam por um traço (-), seguido de qualquer coisa (o ponto quando usado como uma expressão regular significa qualquer coisa), novamente seguido de qualquer coisa, vindo a seguir um x ou um s.
Obteríamos o mesmo resultado se fizéssemos:
e agilizaríamos a pesquisa.
Vamos Montar uma “cdteca”
Vamos começar a desenvolver programas, acho que a montagem de um banco de dados de músicas é bacana para efeito didático (e útil nesses tempos de downloads de mp3 e “queimadores” de CDs). Não se esqueça que, da mesma forma que vamos desenvolver um monte de programas para organizar os seus CDs de música, com pequenas adaptações, você pode fazer o mesmo com os CDs de software que vêm com a Linux Magazine e outros que você compra ou queima, disponibilizando este banco de software para todos que trabalham com você (o Linux é multiusuário, e como tal deve ser explorado), desta forma ganhando muitos pontos com seu adorado chefe.
- Péra ai! De onde eu vou receber os dados dos CDs?
- Inicialmente, vou lhe mostrar como o seu programa pode receber parâmetros de quem o estiver executando e em breve, ensinarei a ler os dados pela tela ou de um arquivo.
Passando parâmetros
O layout do arquivo musicas será o seguinte:
nome do álbum^intérprete1~nome da música1:..:intérprete~nome da música
isto é, o nome do álbum será separado por um circunflexo (^) do resto do registro, que é formado por diversos grupos compostos pelo intérprete de cada música do CD e a respectiva música interpretada. Estes grupos são separados entre si por dois-pontos (:) e internamente, o intérprete será separado por um til (~) do nome da música.
Eu quero escrever um programa que chamado musinc, que incluirá registros no meu arquivo musicas. Eu passarei o conteúdo de cada álbum como parâmetro na chamada do programa fazendo assim:
Desta forma o programa musinc estará recebendo os dados de cada álbum como se fosse uma variável. A única diferença entre um parâmetro recebido e uma variável é que os primeiros recebem nomes numéricos (nome numérico fica muito esquisito, né? O que quis dizer é que seus nomes são formados por um e somente um algarismo), isto é $1, $2, $3, ..., $9. Vamos, antes de tudo, fazer um teste:
Exemplos
#!/bin/bash
# Programa para testar passagem de parametros
echo “1o. parm -> $1″
echo “2o. parm -> $2″
echo “3o. parm -> $3″
Vamos executá-lo:
bash: teste: cannot execute
Ops! Esqueci-me de torná-lo executável. Vou fazê-lo de forma a permitir que todos possam executá-lo e em seguida vou testá-lo:
$ teste passando parametros para testar
1o. parm -> passando
2o. parm -> parametros
3o. parm -> para
Repare que a palavra testar, que seria o quarto parâmetro, não foi listada. Isto deu-se justamente porque o programa teste só listava os três primeiros parâmetros. Vamos executá-lo de outra forma:
1o. parm -> passando parametros
2o. parm -> para
3o. parm -> testar
As aspas não deixaram o Shell ver o espaço em branco entre as palavras e considerou-as um único parâmetro.
Macetes paramétricos
Já que estamos falando em passagem de parâmetros deixa eu te dar mais umas dicas:
| Significado das Principais Variáveis Referentes aos Parâmetros | |
|---|---|
| Variável | Significado |
$0 |
Contém o nome do programa |
$# |
Contém a quantidade de parâmetros passados |
$* |
Contém o conjunto de todos os parâmetros (muito parecido com $@) |
Exemplos
Vamos alterar o programa teste para usar as variáveis que acabamos de ver. Vamos fazê-lo assim:
#!/bin/bash
# Programa para testar passagem de parametros (2a. Versao)
echo O programa $0 recebeu $# parametros
echo “1o. parm -> $1″
echo “2o. parm -> $2″
echo “3o. parm -> $3″
echo Todos de uma só “tacada”: $*
Repare que antes das aspas eu usei uma barra invertida para o escondê-las da interpretação do Shell (se não usasse as contrabarras as aspas não apareceriam). Vamos executá-lo:
O programa teste recebeu 4 parametros
1o. parm -> passando
2o. parm -> parametros
3o. parm -> para
Todos de uma só “tacada”: passando parametros para testar
Conforme eu disse, os parâmetros recebem números de 1 a 9, mas isso não significa que não posso usar mais de 9 parâmetros significa somente que só posso endereçar 9. Vamos testar isso:
Exemplo:
#!/bin/bash
# Programa para testar passagem de parametros (3a. Versao)
echo O programa $0 recebeu $# parametros
echo “11o. parm -> $11″
shift
echo “2o. parm -> $1″
shift 2
echo “4o. Parm -> $1″
Vamos executá-lo:
O programa teste recebeu 4 parametros que são:
11o. parm -> passando1
2o. parm -> parametros
4o. parm -> testar
Duas coisas muito interessantes neste script:
- Para mostrar que os nomes dos parâmetros variam de
$1a$9eu fiz um echo$11e o que aconteceu? O Shell interpretou como sendo$1seguido do algarismo1e listoupassando1; - O comando
shiftcuja sintaxe éshift n, podendo onassumir qualquer valor numérico (porém seu default é1como no exemplo dado), despreza osnprimeiros parâmetros, tornando o parâmetro de ordemn+1, o primeiro ou seja, o$1.
Bem, agora que você já sabe mais sobre passagem de parâmetros do que eu, vamos voltar à nossa “cdteca” para fazer o script de inclusão de CDs no meu banco chamado musicas. O programa é muito simples (como tudo em Shell) e vou listá-lo para você ver:
Exemplos
#!/bin/bash
# Cadastra CDs (versao 1)
#
echo $1 >> musicas
O script é fácil e funcional, limito-me a anexar ao fim do arquivo musicas o parâmetro recebido. Vamos cadastrar 3 álbuns para ver se funciona (para não ficar “enchendo lingüiça”, vou supor que em cada CD só existem 2 músicas):
$ musinc “album 1^Artista1~Musica1:Artista2~Musica2″
$ musinc “album 2^Artista3~Musica3:Artista4~Musica4″
Listando o conteúdo de musicas.
album 3^Artista5~Musica5:Artista6~Musica6
album 1^Artista1~Musica1:Artista2~Musica2
album 2^Artista3~Musica3:Artista4~Musica4
Não está funcional como achava que deveria ficar… podia ter ficado melhor. Os álbuns estão fora de ordem, dificultando a pesquisa. Vamos alterar nosso script e depois testá-lo novamente:
#!/bin/bash
# Cadastra CDs (versao 2)
#
echo $1 >> musicas
sort musicas -o musicas
Vamos cadastrar mais um:
Agora vamos ver o que aconteceu com o arquivo musicas:
album 1^Artista1~Musica1:Artista2~Musica2
album 2^Artista3~Musica3:Artista4~Musica4
album 3^Artista5~Musica5:Artista6~Musica5
album 4^Artista7~Musica7:Artista8~Musica8
Simplesmente inseri uma linha que classifica o arquivo musicas dando a saída nele mesmo (para isso serve a opção -o), após cada álbum ser anexado.
Oba! Agora está legal e quase funcional. Mas atenção, não se desespere! Esta não é a versão final. O programa ficará muito melhor e mais amigável, em uma nova versão que desenvolveremos após aprendermos a adquirir os dados da tela e formatar a entrada.
Exemplos
Ficar listando com o comando cat não está com nada, vamos então fazer um programa chamado muslist para listar um álbum cujo nome será passado como parâmetro:
#!/bin/bash
# Consulta CDs (versao 1)
#
grep $1 musicas
Vamos executá-lo, procurando pelo album 2. Como já vimos antes, para passar a cadeia album 2 é necessário protegê-la da interpretação do Shell, para que ele não a interprete como dois parâmetros. Vamos fazer assim:
grep: can’t open 2
musicas: album 1^Artista1~Musica1:Artista2~Musica2
musicas: album 2^Artista3~Musica3:Artista4~Musica4
musicas: album 3^Artista5~Musica5:Artista6~Musica6
musicas: album 4^Artista7~Musica7:Artista8~Musica8
Que lambança! Onde está o erro? Eu tive o cuidado de colocar o parâmetro passado entre aspas, para o Shell não dividi-lo em dois!
É, mas repare como está o grep executado:
grep $1 musicas
Mesmo colocando álbum 2 entre aspas, para que fosse encarado como um único parâmetro, quando o $1 foi passado pelo Shell para o comando grep, transformou-se em dois argumentos. Desta forma o conteúdo final da linha, que o comando grep executou foi o seguinte:
grep album 2 musicas
Como a sintaxe do grep é:
=grep [arq1, arq2, ..., arqn]=
o grep entendeu que deveria procurar a cadeia de caracteres album nos arquivos 2 e musicas, Por não existir o arquivo 2 gerou o erro, e por encontrar a palavra album em todos os registros de musicas, listou a todos.
Sempre que a cadeia de caracteres a ser passada para o comando grep possuir brancos ou TAB, mesmo que dentro de variáveis, coloque-a sempre entre aspas para evitar que as palavras após o primeiro espaço em branco ou TAB sejam interpretadas como nomes de arquivos.Por outro lado, é melhor ignorarmos maiúsculas e minúsculas na pesquisa. Resolveríamos os dois problemas se o programa tivesse a seguinte forma:
#!/bin/bash
# Consulta CDs (versao 2)
#
grep -i “$1″ musicas
Neste caso, usamos a opção -i do grep, que como já vimos, serve para ignorar maiúsculas e minúsculas, e colocamos o $1 entre aspas, para que o grep continuasse a ver a cadeia de caracteres resultante da expansão da linha pelo Shell como um único argumento de pesquisa.
album2^Artista3~Musica3:Artista4~Musica4
Agora repare que o grep localiza a cadeia pesquisada em qualquer lugar do registro, então da forma que estamos fazendo, podemos pesquisar por álbum, por música, por intérprete ou até por um pedaço de qualquer um destes. Quando conhecermos os comandos condicionais, montaremos uma nova versão de muslist que permitirá especificar por qual campo pesquisar.
Aí você vai me dizer:
- Poxa, mas é um saco ter que colocar o argumento de pesquisa entre aspas na hora de passar o nome do álbum. Esta forma não é nem um pouco amigável!
- Tem razão, e por isso vou te mostrar uma outra forma de fazer o que você pediu:
#!/bin/bash
# Consulta CDs (versao 3)
#
grep -i “$*” musicas
$ muslist album 2
album 2^Artista3~Musica3:Artista4~Musica4
Desta forma, o $*, que significa todos os parâmetros, será substituído pela cadeia album 2 (de acordo com o exemplo anterior, fazendo o que você queria.
Não se esqueça o problema do Shell não é se você pode ou não fazer uma determinada coisa. O problema é decidir qual é a melhor forma de fazê-la, já que para desempenhar qualquer tarefa, a quantidade de opções é enorme.
Ah! Em um dia de verão você foi à praia, esqueceu o CD no carro, aquele “solzinho” de 40 graus empenou o seu CD e agora você precisa de uma ferramenta para removê-lo do banco de dados? Não tem problema, vamos desenvolver um script chamado musexc, para excluir estes CDs.
Antes de desenvolver o “bacalho”, quero te apresentar a uma opção bastante útil da família de comandos grep. É a opção -v, que quando usada lista todos os registros da entrada, exceto o(s) localizado(s) pelo comando. Vejamos:
Exemplos
album 1^Artista1~Musica1:Artista2~Musica2
album 3^Artista5~Musica5:Artista6~Musica6
album 4^Artista7~Musica7:Artista8~Musica8
Conforme eu expliquei antes, o grep do exemplo listou todos os registros de musicas exceto o referente a album 2, porque atendia ao argumento do comando. Estamos então prontos para desenvolver o script para remover aquele CD empenado da sua “CDteca”. Ele tem a seguinte cara:
#!/bin/bash
# Exclui CDs (versao 1)
#
grep -v “$1″ musicas > /tmp/mus$$
mv -f /tmp/mus$$ musicas
Na primeira linha mandei para /tmp/mus$$ o arquivo musicas, sem os registros que atendessem a consulta feita pelo comando grep. Em seguida, movi (que, no duro, equivale a renomear) /tmp/mus$$ por cima do antigo musicas.
Usei o arquivo /tmp/mus$$ como arquivo de trabalho, porque como já havia citado no artigo anterior, o $$ contém o PID (Process Identification ou identificação do processo) e desta forma cada um que editar o arquivo musicas o fará em um arquivo de trabalho diferente, desta forma evitando colisões no uso.
- Aê cara, estes programas que fizemos até aqui estão muito primários em virtude da falta de ferramentas que ainda temos. Mas é bom, enquanto eu tomo mais um chope, você ir para casa praticar em cima dos exemplos dados porque, eu prometo, chegaremos a desenvolver um sistema bacana para controle dos seus CDs.
- Quando nos encontrarmos da próxima vez, vou te ensinar como funcionam os comandos condicionais e aprimoraremos mais um pouco estes scripts.
- Por hoje chega! Já falei demais e preciso molhar a palavra porque estou de goela seca!
- Garçom! Mais um sem colarinho!
Vou aproveitar também para mandar o meu jabá: diga para os amigos que quem estiver afim de fazer um curso porreta de programação em Shell que mande um e-mail para a nossa gerencia de treinamento para informar-se.
Qualquer dúvida ou falta de companhia para um chope ou até para falar mal dos políticos é só mandar um e-mail para mim.
Valeu!