Sie sind auf Seite 1von 46

SHELL SCRIPT

PROFISSIONAL

Aurlio Marinho Jargas

Novatec

Copyright 2008 da Novatec Editora Ltda. Todos os direitos reservados e protegidos pela Lei 9.610 de 19/02/1998. proibida a reproduo desta obra, mesmo parcial, por qualquer processo, sem prvia autorizao, por escrito, do autor e da Editora. Editor: Rubens Prates Reviso gramatical: Maria Rita Quintella Capa: Alex Lutkus ISBN: 978-85-7522-152-5 Histrico de impresses: Outubro/2011 Janeiro/2011 Maro/2010 Outubro/2008 Abril/2008 Quarta reimpresso Terceira reimpresso Segunda reimpresso Primeira reimpresso Primeira edio

Novatec Editora Ltda. Rua Lus Antnio dos Santos 110 02460-000 So Paulo, SP, Brasil Tel.: +55 11 2959-6529 Fax: +55 11 2950-8869 Email: novatec@novatec.com.br Site: www.novatec.com.br Twitter: twitter.com/novateceditora Facebook: facebook.com/novatec LinkedIn: linkedin.com/in/novatec
Dados Internacionais de Catalogao na Publicao (Cmara Brasileira do Livro, SP, Brasil)
Jargas, Aurlio Marinho Shell Script Profissional / Aurlio Marinho Jargas. -- So Paulo : Novatec Editora, 2008. ISBN 978-85-7522-152-5 1. Shell Script (Programa de computador) I. Ttulo.

(CIP)

08-01176

CDD-005.369 ndices para catlogo sistemtico: 1. Shell Script : Computadores : Programas : Processamento de dados 005.369

Prefcio

Em 1997, fiz meu primeiro script em shell. Eu tinha 20 anos, era estagirio na Conectiva (hoje Mandriva) e ainda estava aprendendo o que era aquele tal de Linux. Hoje tudo colorido e bonitinho, mas h uma dcada usar o Linux significava passar a maior parte do tempo na interface de texto, a tela preta, digitando comandos no terminal. Raramente, quando era preciso rodar algum aplicativo especial, digitava-se startx para iniciar o modo grfico. Meu trabalho era dar manuteno em servidores Linux, ento o domnio da linha de comando era primordial. O Arnaldo Carvalho de Melo (acme) era meu tutor e ensinou o bsico sobre o uso do terminal, comandos, opes, redirecionamento e filtragem (pipes). Mas seu ensinamento mais valioso, que at hoje reflete em minha rotina de trabalho, veio de maneira indireta: ele me forava a ler a documentao disponvel no sistema. A rotina era sempre a mesma: o Arnaldo sentava-se e demonstrava como fazer determinada tarefa, digitando os comandos, para que eu visse e aprendesse. Porm, era como acompanhar uma corrida de Frmula-1 no meio da reta de chegada. Ele digitava rpido, falava rpido e, com seu vasto conhecimento, transmitia muito mais informaes do que minha mente leiga era capaz de absorver naqueles poucos minutos. Quando ele voltava para sua mesa, ento comeava meu lento processo de assimilao. Primeiro, eu respirava fundo, sabendo que as prximas horas iriam fatigar os neurnios. Com aquele semblante murcho de quem no sabe nem por onde comear, eu dava um history para ver quais eram todos aqueles comandos estranhos que ele havia digitado. E eram muitos. Para tentar entender cada comando, primeiro eu lia a sua mensagem de ajuda (help) para ter uma ideia geral de sua utilidade. Depois, lia a sua man page, da primeira ltima linha, para tentar aprender o que aquele comando fazia. S ento arriscava fazer alguns testes na linha de comando para ver o funcionamento. Experimentava algumas opes, porm com cautela, pensando bem antes de apertar o Enter. Ler uma man page era uma experincia frustrante. Tudo em ingls, no tinha exemplos e o texto parecia ter sido sadicamente escrito da maneira mais enigmtica

19

20

Shell Script Profissional

possvel, confundindo em vez de ajudar. A pior de todas, com larga vantagem no nvel de frustrao, era a temida man bash, com suas interminveis pginas que eu lia, relia, trelia e no entendia nada. Ah, meu ingls era de iniciante, o que tornava o processo ainda mais cruel. A Internet no ajudava em quase nada, pois as fontes de informao eram escassas. S havia algumas poucas pginas em ingls, com raros exemplos. Fruns? Blogs? Lista de discusso? Passo-a-passo-receita-de-bolo? Esquea. Era man page e ponto. Mas com o tempo fui me acostumando com aquela linguagem seca e tcnica das man pages e aos poucos os comandos comearam a me obedecer. Os odiosos command not found e syntax error near unexpected token tornaram-se menos frequentes. Deixei de perder arquivos importantes por ter digitado um comando errado, ou por ter usado > ao invs de >>. Dica: Leia man pages. chato, cansativo e confuso, mas compensa. Afinal, quem aprende a dirigir em um jipe velho, dirige qualquer carro.

Quanto mais aprendia sobre os comandos e suas opes, mais conseguia automatizar tarefas rotineiras do servidor, fazendo pequenos scripts. Cdigos simples, com poucos comandos, mas que poupavam tempo e garantiam a padronizao na execuo. Ainda tenho alguns deles aqui no meu $HOME, vejamos: mudar o endereo IP da mquina, remover usurio, instalao do MRTG, gerar arquivo de configurao do DHCPD. Em alguns meses j eram dezenas de scripts para as mais diversas tarefas. Alguns mais importantes que outros, alguns mais complexos: becape, gravao de CDs, gerao de pginas HTML, configurao de servios, conversores, wrappers e diversas pequenas ferramentas para o controle de qualidade do Conectiva Linux. Que verstil o shell! Mas nem tudo era flores. Alguns scripts cresceram alm de seu objetivo inicial, ficando maiores e mais complicados. Cada vez era mais difcil encontrar o lugar certo para fazer as alteraes, tomando o cuidado de no estragar seu funcionamento. Outros tcnicos tambm participavam do processo de manuteno, adicionando funcionalidades e corrigindo bugs, ento era comum olhar um trecho novo do cdigo que demorava um bom tempo at entender o que ele fazia e por que estava ali. Era preciso amadurecer. Mais do que isso, era preciso fazer um trabalho mais profissional.

Prefcio

21

Os scripts codificados com pressa e sem muito cuidado com o alinhamento e a escolha de nomes de variveis estavam se tornando um pesadelo de manuteno. Muito tempo era perdido em anlise, at realmente saber onde e o que alterar. Os scripts de cinco minutinhos precisavam evoluir para programas de verdade. Cabealhos informativos, cdigo comentado, alinhamento de blocos, espaamento, nomes descritivos para variveis e funes, registro de mudanas, versionamento. Estes eram alguns dos componentes necessrios para que os scripts confusos fossem transformados em programas de manuteno facilitada, poupando nosso tempo e, consequentemente, dinheiro. A mudana no foi traumtica, e j nos primeiros programas provou-se extremamente bem-sucedida. Era muito mais fcil trabalhar em cdigos limpos e bemcomentados. Mesmo programas complexos no intimidavam tanto, pois cada bloco lgico estava visualmente separado e documentado. Bastava ler os comentrios para saber em qual trecho do cdigo era necessria a alterao. Quanta diferena! Dica: Escreva cdigos legveis. Cada minuto adicional investido em limpeza de cdigo e documentao compensa. No basta saber fazer bons algoritmos, preciso torn-los acessveis.

Todo este processo fez-me evoluir como profissional e como programador. Primeiro, aprendi a procurar por conhecimento em vez de cdigos prontos para copiar e colar. Depois, aprendi o valor imenso de um cdigo limpo e informativo. Ento, nasceu este livro de shell. Ao olhar para o Sumrio, voc ver uma lista de tecnologias e tcnicas para tornar seus scripts mais poderosos. Mas no quero que voc simplesmente aprenda a fazer um CGI ou consiga entender expresses regulares. Quero que voc tambm evolua. Quero que voc faa programas de verdade em vez de meros scripts toscos. Prepare-se para uma imerso em shell. Ao ler cada captulo, voc far um mergulho profundo no assunto, aprendendo bem os fundamentos para depois poder aplic-los com a segurana de quem sabe o que est fazendo. Os cdigos no so entregues prontos em uma bandeja. Passo a passo vamos juntos construindo os pedaos dos programas, analisando seu funcionamento, detectando fraquezas e melhorando at chegar a uma verso estvel. Voc no ficar perdido, pois avanaremos conforme os conceitos so aprendidos. Imagine-se lendo o manual de seu primeiro carro zero quilmetro, sentado confortavelmente no banco do motorista, sentindo aquele cheirinho bom de carro

22

Shell Script Profissional

novo. Voc no tem pressa, pode ficar a noite toda saboreando aquelas pginas, testando todos os botes daquele painel reluzente. Se o manual diz que a luz acender ao apertar o boto vermelho, o que voc faz imediatamente? Aperta o boto e sorri quando a luz acende. De maneira similar, estude este livro com muita calma e ateno aos detalhes. No salte pargrafos, no leia quando estiver com pressa. Reserve um tempo de qualidade para seus estudos. Com um computador ao alcance das mos, teste os conceitos durante a leitura. Digite os comandos, faa variaes, experimente! Brinque na linha de comando, aprendendo de maneira prazerosa. Vamos?
Visite o site do livro (www.shellscript.com.br) para baixar os cdigos-fonte de todos os programas que estudaremos a seguir.

Captulo 4
Opes de linha de comando (-f, --foo)

Trazer mais opes e possibilidades para o usurio algo que faz parte da evoluo natural de um programa. Mas no elegante forar o usurio a editar o cdigo para alterar o valor de variveis, cada vez que precisar de um comportamento diferente. Aprenda a fazer seu programa reconhecer opes de linha de comando, curtas e longas, tornando-o mais flexvel e amigvel ao usurio.

57

58

Shell Script Profissional

Voc j est bem-acostumado a usar opes para as ferramentas do sistema. um -i no grep para ignorar a diferena das maisculas e minsculas, um -n no sort para ordenar numericamente, um -d e um -f no cut para extrair campos, um -d no tr para apagar caracteres. Usar uma opo rpido e fcil, basta ler a man page ou o --help e adicion-la no comando. Agora, responda-me rpido: o que impede que os seus prprios programas tambm tenham opes de linha de comando? No seria interessante o seu programa agir como qualquer outra ferramenta do sistema? Neste captulo aprenderemos a colocar opes em nossos programas. Com isso, alm da melhor integrao com o ambiente (sistema operacional), melhoramos a interface com o usurio, que j est acostumado a usar opes e poder facilmente informar dados e alterar o comportamento padro do programa.

O formato padro para as opes


No h uma conveno ou padro internacional que force um programa a usar este ou aquele formato para ler parmetros e argumentos do usurio. Ao longo das dcadas, alguns formatos foram experimentados, porm hoje podemos constatar que a maioria usa o formato adotado pelos aplicativos GNU. Acompanhe a anlise. Em um sistema Unix, que possui aplicativos com mais idade que muitos leitores deste livro, variada a forma que os programas esperam receber as opes de linha de comando. Uma grande parte usa o formato de opes curtas (de uma letra) com o hfen, mas no h um padro. Veja alguns exemplos:

Formato das opes de programas no Unix


Comando
find ps dd

Formato
-<palavra> <letra> <palavra>=

Exemplos
-name,-type a u w x if=, of=, count=

H tambm os aplicativos GNU, que de uma forma ou outra esto ligados FSF (Free Software Foundation) e preferem a licena GPL. Estes so programas com cdigo mais recente, que vieram para tentar substituir os aplicativos do Unix. Esto presentes em todos os cantos de um sistema Linux, alguns exemplares habitam no Mac e tambm podem ser instalados no Windows por intermdio do Cygwin. Eles seguem um padro que no forado, porm recomendado e adotado pela maioria.

Captulo 4 Opes de linha de comando (-f, --foo)

59

Formato das opes de aplicativos GNU


Formato
-<letra> --<palavra>

Exemplos
-h, -V

Descrio Opes curtas, de uma letra

--help, --version Opes longas, de uma ou mais palavras

Por ser um formato mais consistente e hoje amplamente utilizado, acostume-se a us-lo. Este um padro inclusive para outras linguagens de programao como Python e Perl (por meio dos mdulos getopt).
Para pensar na cama: Os aplicativos GNU so considerados mais modernos e geralmente possuem mais opes e funcionalidades do que seus parentes do Unix. Essa abundncia de opes pode ser benfica ou no, dependendo do seu ponto de vista. O GNU sort deveria mesmo ter a opo -u sendo que j existe o uniq para fazer isso? E o GNU ls com suas mais de 80 opes, incluindo a prola --dereference-command-line-symlink-todir, no seria um exagero? Onde fica o limite do faa apenas UMA tarefa e a faa bemfeita? Quantidade reflete qualidade? Evoluo ou colesterol?

Opes clssicas para usar em seu programa


H algumas opes clssicas que so usadas pela grande maioria dos programas para um propsito comum. Por exemplo, o -h ou --help usado para mostrar uma tela de ajuda. Voc j est acostumado a usar esta opo em vrias ferramentas do sistema, ento, quando for implementar uma tela de ajuda em seu programa, no invente! -h e --help. No surpreenda o usurio ou force-o a aprender novos conceitos sem necessidade. Algumas opes esto to presentes por todo o sistema que considerado um abuso utiliz-las para outros propsitos que no os j estabelecidos. Algumas excees so programas antigos do Unix que j usavam estas letras para outras funes. Mas voc, como novato na vizinhana, siga as regras e faa seu programa o mais parecido com os j existentes. O uso das seguintes opes fortemente recomendado (caso aplicvel):

Opes estabelecidas
Opo curta
-h -V -v -q

Opo longa
--help --version --verbose --quiet --

Descrio Mostra informaes resumidas sobre o uso do programa e sai. Mostra a verso do programa e sai (V maisculo). Mostra informaes adicionais na sada, informando o usurio sobre o estado atual da execuo. No mostra nada na sada, uma execuo quieta. Terminador de opes na linha de comando, o que vem depois dele no considerado opo.

60

Shell Script Profissional

H outras opes que no so um padro global, mas que so vistas em vrios aplicativos com o mesmo nome e funo similar. Ento, se aplicvel ao seu programa, aconselhvel que voc use estes nomes em vez de inventar algo novo:

Opes recomendadas
Opo curta
-c -d -f -i -n -o -w

Opo longa
--chars --delimiter --file --ignore-case --number --output --word

Descrio Algo com caracteres Caractere(s) usado(s) como delimitador, separador Nome do arquivo a ser manipulado Trata letras maisculas e minsculas como iguais Algo com nmeros Nome do arquivo de sada Algo com palavras

Exemplo
cut -c, od -c, wc -c cut -d, paste -d grep -f, sed -f grep -i, diff -i cat -n, grep -n, head -n, xargs -n sort -o, gcc -o grep -w, wc -w

Como adicionar opes a um programa


Chega de teoria, no mesmo? Est na hora de fazer a parte divertida desse negcio: programar. Vamos acompanhar passo a passo a incluso de vrias opes a um programa j existente. Por falar nele, aqui est:

usuarios.sh
#!/bin/bash # # usuarios.sh # Mostra os logins e nomes de usurios do sistema # Obs.: L dados do arquivo /etc/passwd # # # Aurlio, Novembro de 2007

cut -d : -f 1,5 /etc/passwd | tr : \\t

Este um programa bem simples que mostra uma listagem dos usurios do sistema, no formato login TAB nome completo . Seu cdigo resume-se a uma nica linha com um cut que extrai os campos desejados do arquivo de usurios (/etc/passwd) e um tr filtra esta sada transformando todos os dois-pontos em TABs. Veja um exemplo de sua execuo:

Captulo 4 Opes de linha de comando (-f, --foo)


$ ./usuarios.sh root uucp lp

61

daemon System Services

System Administrator Unix to Unix Copy Protocol Printing Services

postfix Postfix User www eppc sshd qtss

World Wide Web Server Apple Events User MySQL Server

mysql

sshd Privilege separation

mailman Mailman user amavisd Amavisd User jabber Jabber User tokend Token Daemon unknown Unknown User $

QuickTime Streaming Server

Realmente simples, no? Nas prximas pginas, esta pequena gema de uma linha crescer para suportar opes e aumentar um pouco a sua gama de funcionalidades. Mas, claro, sem perder o foco inicial: mostrar a lista de usurios. Nada de ler e-mail ou trazer um Wiki embutido ;)

Adicionando as opes -h, -V, --help e --version


Nossa primeira tarefa ser adicionar as duas opes mais clssicas de qualquer programa: a -h para ajuda e a -V para obter a verso. Alguma ideia de como fazer isso? Vou dar um tempinho para voc pensar. Como pegar as opes que o usurio digitou? Como reconhecer e processar estas opes? E se o usurio passar uma opo invlida, o que acontece? E a, muitas ideias? Vamos comear simples, fazendo a opo -h para mostrar uma tela de ajuda para o programa. O primeiro passo saber exatamente quais foram as opes que o usurio digitou, e, caso tenha sido a -h, process-la. Para saber o que o usurio digitou na linha de comando, basta conferir o valor das variveis posicionais $1, $2, $3 e amigos. Em $0 fica o nome do programa, em $1 fica o primeiro argumento, em $2 o segundo, e assim por diante.
cut -d : -f 2 /etc/passwd
$0 $1 $2 $3 $4 $5

Parmetros posicionais $0, $1, $2, ...

62

Shell Script Profissional

Ento, se nosso programa foi chamado como usuarios.sh -h, a opo estar guardada em $1. A ficou fcil. Basta conferir se o valor de $1 -h, em caso afirmativo, mostramos a tela de ajuda e samos do programa. Traduzindo isso para shell fica:

usuarios.sh (v2)
#!/bin/bash # # usuarios.sh # Mostra os logins e nomes de usurios do sistema # Obs.: L dados do arquivo /etc/passwd #

# Verso 1: Mostra usurios e nomes separados por TAB # Verso 2: Adicionado suporte opo -h
# # # Aurlio, Novembro de 2007

MENSAGEM_USO=" Uso: $0 [-h] -h " # Tratamento das opes de linha de comando if test "$1" = "-h" then fi
# Processamento

Mostra esta tela de ajuda e sai

echo "$MENSAGEM_USO" exit 0

cut -d : -f 1,5 /etc/passwd | tr : \\t

L nos cabealhos deixamos claro que esta a segunda verso do programa e que ela traz de novidade a opo -h. A mensagem de uso guardada em uma varivel, usando o $0 para obter o nome do programa. Isso bom porque podemos mudar o nome do programa sem precisar nos preocupar em mudar a mensagem de ajuda junto. Em seguida, um if testa o contedo de $1, se for o -h que queremos, mostra a ajuda e sai com cdigo de retorno zero, que significa: tudo certo.
$ ./usuarios-2.sh -h Uso: ./usuarios-2.sh [-h] -h Mostra esta tela de ajuda e sai

Captulo 4 Opes de linha de comando (-f, --foo)


$ ./usuarios-2.sh -X root uucp lp System Administrator Unix to Unix Copy Protocol Printing Services

63

daemon System Services

postfix Postfix User www eppc sshd qtss

World Wide Web Server Apple Events User MySQL Server

mysql

sshd Privilege separation

mailman Mailman user amavisd Amavisd User jabber Jabber User tokend Token Daemon unknown Unknown User $

QuickTime Streaming Server

Funciona! Quando passamos o -h, foi mostrada somente a mensagem de ajuda e o programa foi terminado. J quando passamos a opo -X, ela foi ignorada e o programa continuou sua execuo normal. Como fcil adicionar opes a um programa em shell!
Na mensagem de ajuda, o [-h] entre colchetes indica que este parmetro opcional, ou seja, voc pode us-lo, mas no obrigatrio.

De maneira similar, vamos adicionar a opo -V para mostrar a verso do programa. Novamente testaremos o contedo de $1, ento basta adicionar mais um teste ao if, desta vez procurando por -V:
# Tratamento das opes de linha de comando if test "$1" = "-h" then

echo "$MENSAGEM_USO" exit 0

elif test "$1" = "-V" then


fi

# mostra a verso

...

E a cada nova opo, o if vai crescer mais. Mmmm, espera. Em vez de fazer um if monstruoso, cheio de braos, aqui bem melhor usar o case. De brinde ainda ganhamos a opo * para pegar as opes invlidas. Veja como fica melhor e mais legvel:

64
# Tratamento das opes de linha de comando case "$1" in -h)

Shell Script Profissional

echo "$MENSAGEM_USO" exit 0

;; -V) ;; *) esac ;;

# mostra a verso

# opo invlida

Para a opo invlida fcil, basta mostrar uma mensagem na tela informando ao usurio o erro e sair do programa com cdigo de retorno 1. Um echo e um exit so suficientes. A verso basta mostrar uma nica linha com o nome do programa e a sua verso atual, ento sai com retorno zero. Mais um echo e um exit. Parece fcil.

usuarios.sh (v3)
#!/bin/bash # # usuarios.sh # Mostra os logins e nomes de usurios do sistema # Obs.: L dados do arquivo /etc/passwd #

# Verso 1: Mostra usurios e nomes separados por TAB # Verso 2: Adicionado suporte opo -h # #

# Verso 3: Adicionado suporte opo -V e opes invlidas


# Aurlio, Novembro de 2007

MENSAGEM_USO="

Uso: $0 [-h | -V] -h Mostra esta tela de ajuda e sai

-V
"

Mostra a verso do programa e sai

Captulo 4 Opes de linha de comando (-f, --foo)


# Tratamento das opes de linha de comando case "$1" in -h)

65

echo "$MENSAGEM_USO" exit 0

;; -V)

echo $0 Verso 3 exit 0

;; *) esac

echo Opo invlida: $1 exit 1

;;

# Processamento

cut -d : -f 1,5 /etc/passwd | tr : \\t

Mais uma vez o cabealho foi atualizado para informar as mudanas. Isso deve se tornar um hbito, uma regra que no pode ser quebrada. Acostume-se desde j. Em seguida, a mensagem de uso agora mostra que o programa tambm possui a opo -V. O pipe em [-h | -V] informa que voc pode usar a opo -h ou a opo -V, e ambas so opcionais (indicado pelos colchetes). Depois vem o case com cada opo bem alinhada, tornando a leitura agradvel. No -V mostrada a verso atual e a sada normal. O asterisco vale para qualquer outra opo fora o -h e o -V, e alm de mostrar a mensagem informando que a opo digitada invlida, sai com cdigo de erro (1).
$ ./usuarios-3.sh -h Uso: ./usuarios-3.sh [-h | -V] -h -V Mostra esta tela de ajuda e sai

Mostra a verso do programa e sai

$ ./usuarios-3.sh -V

./usuarios-3.sh Verso 3 $ ./usuarios-3.sh -X Opo invlida: -X Opo invlida: $ $ ./usuarios-3.sh

66

Shell Script Profissional

Tudo funcionando! Ops, quase. Quando no foi passada nenhuma opo, o programa deveria mostrar a lista de usurios normalmente, mas acabou caindo no asterisco do case... Primeira melhoria a ser feita: s testar pela opo invlida se houver $1; caso contrrio, continue.
*) if test -n "$1"

then

;; fi

echo Opo invlida: $1 exit 1

Outra alterao interessante seria eliminar o ./ do nome do programa tanto na opo -h quanto na -V. Ele mostrado porque o $0 sempre mostra o nome do programa exatamente como ele foi chamado, inclusive com PATH. O comando basename vai nos ajudar nesta hora, arrancando o PATH e deixando somente o nome do arquivo.
MENSAGEM_USO=" ... " Uso: $(basename "$0") [-h | -V]

J que estamos aqui, com o -h e o -V funcionando, que tal tambm adicionar suas opes equivalentes --help e --version? Vai ser muito mais fcil do que voc imagina. Lembre-se de que dentro do case possvel especificar mais de uma alternativa para cada bloco? Ento, alterando somente duas linhas do programa ganhamos de brinde mais duas opes.
case "$1" in ... ;; -h | --help)

... ;; *) ;; esac

-V | --version)

...

Captulo 4 Opes de linha de comando (-f, --foo)

67

Uma ltima alterao seria melhorar este esquema de mostrar a verso do programa. Aquele nmero ali fixo dentro do case tende a ser esquecido. Voc vai modificar o programa, lanar outra verso e no vai se lembrar de aumentar o nmero da opo -V. Por outro lado, o nmero da verso est sempre l no cabealho, no registro das mudanas. Ser que...
$ grep '^# Verso ' usuarios-3.sh # Verso 1: Mostra usurios e nomes separados por TAB # Verso 2: Adicionado suporte opo -h # Verso 3: Adicionado suporte opo -V e opes invlidas $ grep '^# Verso ' usuarios-3.sh | tail -1

# Verso 3: Adicionado suporte opo -V e opes invlidas # Verso 3 Verso 3

$ grep '^# Verso ' usuarios-3.sh | tail -1 | cut -d : -f 1

$ grep '^# Verso ' usuarios-3.sh | tail -1 | cut -d : -f 1 | tr -d \# $

Ei, isso foi legal! Alm de extrair a verso do programa automaticamente, ainda nos foramos a sempre registrar nos cabealhos o que mudou na verso nova, para no quebrar o -V. Simples e eficiente. Vejamos como ficou esta verso nova do cdigo.

usuarios.sh (v4)
#!/bin/bash # # usuarios.sh # Mostra os logins e nomes de usurios do sistema # Obs.: L dados do arquivo /etc/passwd #

# Verso 1: Mostra usurios e nomes separados por TAB # Verso 2: Adicionado suporte opo -h # Verso 3: Adicionado suporte opo -V e opes invlidas

# Verso 4: Arrumado bug quando no tem opes, basename no # #


# #

nome do programa, -V extraindo direto dos cabealhos, adicionadas opes --help e --version

# Aurlio, Novembro de 2007

MENSAGEM_USO="

Uso: $(basename "$0") [-h | -V]

68
-h, --help " -V, --version Mostra esta tela de ajuda e sai Mostra a verso do programa e sai

Shell Script Profissional

# Tratamento das opes de linha de comando case "$1" in -h | --help) exit 0

echo "$MENSAGEM_USO"

;;

echo -n $(basename "$0")


;; *)

-V | --version)

# Extrai a verso diretamente dos cabealhos do programa grep '^# Verso ' "$0" | tail -1 | cut -d : -f 1 | tr -d \#
exit 0

if test -n "$1"
echo Opo invlida: $1 exit 1

then
;; esac

fi

# Processamento

cut -d : -f 1,5 /etc/passwd | tr : \\t

Agora, sim, podemos considerar que o programa ficou estvel aps a adio de quatro opes de linha de comando, alm do tratamento de opes desconhecidas. Antes de continuar adicionando opes novas, bom conferir se est mesmo tudo em ordem com o cdigo atual.
$ ./usuarios-4.sh -h Uso: usuarios-4.sh [-h | -V] -h, --help Mostra esta tela de ajuda e sai

-V, --version

Mostra a verso do programa e sai

Captulo 4 Opes de linha de comando (-f, --foo)


$ ./usuarios-4.sh --help Uso: usuarios-4.sh [-h | -V] -h, --help Mostra esta tela de ajuda e sai

69

-V, --version

Mostra a verso do programa e sai

usuarios-4.sh Verso 4 usuarios-4.sh Verso 4 Opo invlida: --foo Opo invlida: -X root uucp lp

$ ./usuarios-4.sh -V

$ ./usuarios-4.sh --version $ ./usuarios-4.sh --foo $ ./usuarios-4.sh -X $ ./usuarios-4.sh

daemon System Services

System Administrator Unix to Unix Copy Protocol Printing Services

postfix Postfix User www eppc sshd qtss

World Wide Web Server Apple Events User MySQL Server

mysql

sshd Privilege separation

mailman Mailman user amavisd Amavisd User jabber Jabber User tokend Token Daemon unknown Unknown User $

QuickTime Streaming Server

Adicionando opes especficas do programa


Colocar as opes clssicas no programa foi fcil, no? Um case toma conta de tudo, cada opo tendo seu prprio cantinho dentro do programa. Faz o que tem que fazer e termina com um exit de zero ou um para informar se est tudo bem. Se o fluxo de execuo no entrou em nenhum destes cantinhos, o programa segue sua rota normal, mostrando a lista de usurios do sistema.

70

Shell Script Profissional

Agora vamos adicionar algumas opes que possuem um comportamento diferente. Elas vo ter seu cantinho dentro do case, mas em vez de terminar a execuo do programa, vo definir variveis e no final a lista de usurios tambm dever ser mostrada. Isso vai requerer uma alterao estrutural em nosso programa. Mas vamos com calma. Primeiro, o mais importante avaliar: que tipo de opo seria til adicionar ao nosso programa? Como programadores talentosos e criativos que somos, podemos adicionar qualquer coisa ao cdigo, o cu o limite. Mas ser que uma enorme quantidade de opes reflete qualidade? Adicionar toda e qualquer opo que vier mente realmente benfico ao programa e aos usurios? Avalie o seguinte:

A opo --FOO vai trazer benefcios maioria dos usurios ou apenas a um grupo muito seleto de usurios avanados? Estes usurios avanados j no conseguem se virar sem esta opo? A opo --FOO vai trazer muita complexidade ao cdigo atual? Sua implementao ser muito custosa ao programador? A opo --FOO vai influir no funcionamento da opo --BAR j existente? A opo --FOO est dentro do escopo do programa? Ela no vai descaracterizar seu programa? A opo --FOO auto-explicvel? Ou preciso um pargrafo inteiro para descrever o que ela faz? Se est difcil dar um nome, pode ser um sintoma que ela nem deveria existir em primeiro lugar... A opo --FOO realmente necessria? No possvel fazer a mesma tarefa usando uma combinao das opes j existentes? A opo --FOO realmente necessria? No seria ela apenas cosmtica , no adicionando nenhum real valor ao seu programa? A opo --FOO REALMENTE necessria? :)

A nfase na real necessidade de se colocar uma opo justificada pela tendncia que ns, programadores, temos de ir adicionando funcionalidades no cdigo, sem pensar muito nelas. Afinal, programar divertido! muito melhor ficar horas programando novidades do que perder tempo avaliando se aquilo realmente ser til. Essa tendncia leva a transformar em um monstro indomvel aquele programinha rpido e eficiente das primeiras verses. No incio, o programa tinha poucas opes, mas as executava instantaneamente e com perfeio. Com o passar do tempo, muitas opes foram adicionadas e hoje ele demora para dar uma resposta, s vezes interrompendo sua execuo sem aviso prvio. Deu pau!

Captulo 4 Opes de linha de comando (-f, --foo)

71

Para no ser apenas mais um personagem dessa histria que se repete diariamente, faa um favor a si mesmo: leia a lista anterior toda vez que pensar em adicionar uma opo nova ao seu programa. Pense, analise, avalie, questione. Se a opo passar por todas estas barreiras e provar ser realmente til, implemente-a.
Cara chato, n? Mas se no tiver um chato para te ensinar as coisas chatas (porm importantes), como voc vai evoluir como programador? A chatice de hoje a qualidade de amanh em seu trabalho. Invista no seu potencial e colha os frutos no futuro!

Agora que acabaram os conselhos da terceira-idade, podemos continuar :) Vamos decidir quais opes incluir em nosso programa. No seremos to rigorosos quanto utilidade das opes, visto que o objetivo aqui ser didtico. Mas no seu programa, j sabe... Vejamos, o que o usuarios.sh faz listar os usurios do sistema. Quais variaes desta listagem poderiam ser interessantes ao usurio? Talvez uma opo para que a listagem aparea ordenada alfabeticamente? Isso pode ser til.
$ ./usuarios-4.sh | sort amavisd Amavisd User eppc lp daemon System Services jabber Jabber User

Apple Events User Printing Services MySQL Server QuickTime Streaming Server System Administrator sshd Privilege separation

mailman Mailman user mysql qtss root sshd postfix Postfix User

tokend Token Daemon unknown Unknown User uucp www $

Unix to Unix Copy Protocol World Wide Web Server

Sabemos que existe o comando sort e que ele ordena as linhas. Mas o usurio no obrigado a saber disso. Ele no mximo ler a nossa tela de ajuda e ali saber das possibilidades de uso do programa. Ento, apesar de ter uma implementao trivial, o usurio se beneficiar com esta opo. Nosso programa ter duas opes novas -s e --sort que sero equivalentes e serviro para que a lista seja ordenada.
Sim, poderia ser -o e --ordena para que as opes ficassem em portugus. Mas como j temos --help e --version em ingls, prefervel manter o padro do que misturar os idiomas. Outra opo seria deixar tudo em portugus alterando as opes atuais para ajuda e --versao (sem acentos para evitar problemas!). Avalie o perfil de seus usurios e decida qual idioma utilizar.

72

Shell Script Profissional

Para adicionar esta opo nova temos que inclu-la na mensagem de ajuda, e tambm dentro do case. Alguma varivel global ser necessria para indicar se a sada ser ou no ordenada, e esta opo mudar o valor desta varivel. Um esqueleto deste esquema seria assim:
ordenar=0
... # Tratamento das opes de linha de comando case "$1" in # A sada dever ser ordenada?

-s | --sort) ;;

ordenar=1

... esac ... if test "$ordenar" = 1 then fi # ordena a listagem

No incio do programa desligamos a chave de ordenao ($ordenar), colocando um valor padro zero. Ento so processadas as opes de linha de comando dentro do case. Caso o usurio tenha passado a opo --sort, a chave ligada. L no final do programa h um if que testa o valor da chave, se ela estiver ligada a listagem ordenada. Este o jeito limpo de implementar uma opo nova. Tudo o que ela faz mudar o valor de uma varivel de nosso programa. L na frente o programa sabe o que fazer com essa varivel. O efeito exatamente o mesmo de o usurio editar o programa e mudar o valor padro da chave (ordenar=1) no incio. A vantagem em se usar uma opo na linha de comando evitar que o usurio edite o cdigo, pois assim corre o risco de bagun-lo. Para implementar a ordenao em si, a primeira ideia que vem cabea fazer tudo dentro do if. Bastaria repetir a linha do cut | tr colocando um sort no final e pronto, ambas as possibilidades estariam satisfeitas. Acompanhe:

Captulo 4 Opes de linha de comando (-f, --foo)


if test "$ordenar" = 1 then cut -d : -f 1,5 /etc/passwd | tr : \\t | sort cut -d : -f 1,5 /etc/passwd | tr : \\t

73

else fi

Mas isso traz vrios problemas. O primeiro a repetio de cdigo, o que nunca benfico. Sero dois lugares para mudar caso alguma alterao precise ser feita na dupla cut | tr. Esta soluo tambm no ir ajudar quando precisarmos adicionar outras opes que tambm manipulem a listagem. O melhor fazer cada tarefa isoladamente, para que a independncia entre elas garanta que todas funcionem simultaneamente: primeiro extrai a lista e guarda em uma varivel. Depois ordena, se necessrio.
# Extrai a listagem lista=$(cut -d : -f 1,5 /etc/passwd) if test "$ordenar" = 1 then fi

# Ordena a listagem (se necessrio)

lista=$(echo "$lista" | sort)

# Mostra o resultado para o usurio echo "$lista" | tr : \\t

Assim o cdigo fica maior, porm muito mais flexvel e poderoso. A vantagem da separao das tarefas ficar evidente quando adicionarmos mais opes ao programa. J so muitas mudanas, hora de lanar uma verso nova:

usuarios.sh (v5)
#!/bin/bash # # usuarios.sh # Mostra os logins e nomes de usurios do sistema # Obs.: L dados do arquivo /etc/passwd #

# Verso 1: Mostra usurios e nomes separados por TAB # Verso 2: Adicionado suporte opo -h # Verso 3: Adicionado suporte opo -V e opes invlidas # Verso 4: Arrumado bug quando no tem opes, basename no # # adicionadas opes --help e --version nome do programa, -V extraindo direto dos cabealhos,

74
# Verso 5: Adicionadas opes -s e --sort
# # # Aurlio, Novembro de 2007

Shell Script Profissional

ordenar=0
MENSAGEM_USO="

# A sada dever ser ordenada?

Uso: $(basename "$0") [-h | -V | -s]

-s, --sort
-h, --help " -V, --version

Ordena a listagem alfabeticamente


Mostra esta tela de ajuda e sai Mostra a verso do programa e sai

# Tratamento das opes de linha de comando case "$1" in

-s | --sort)

ordenar=1 ;;
-h | --help) exit 0

echo "$MENSAGEM_USO"

;;

-V | --version)

echo -n $(basename "$0")

# Extrai a verso diretamente dos cabealhos do programa exit 0

grep '^# Verso ' "$0" | tail -1 | cut -d : -f 1 | tr -d \#

;; *)

then

if test -n "$1" echo Opo invlida: $1

exit 1 fi ;; esac

Captulo 4 Opes de linha de comando (-f, --foo)


# Extrai a listagem lista=$(cut -d : -f 1,5 /etc/passwd) # Ordena a listagem (se necessrio) if test "$ordenar" = 1 then lista=$(echo "$lista" | sort) fi # Mostra o resultado para o usurio echo "$lista" | tr : \\t

75

O cdigo est simples de entender, certo que a opo nova vai funcionar. Mas no custa testar, so apenas alguns segundos investidos. E v que aparece algum bug aliengena que nossos olhos terrqueos no consigam captar?
$ ./usuarios-5.sh --sort amavisd Amavisd User eppc lp daemon System Services jabber Jabber User

Apple Events User Printing Services MySQL Server QuickTime Streaming Server System Administrator sshd Privilege separation

mailman Mailman user mysql qtss root sshd postfix Postfix User

tokend Token Daemon unknown Unknown User uucp www $

Unix to Unix Copy Protocol World Wide Web Server

Ufa, no foi desta vez que os ETs tiraram o nosso sono... Agora que o cdigo do programa est com uma boa estrutura, fica fcil adicionar duas opes novas: uma para inverter a ordem da lista e outra para mostrar a sada em letras maisculas. Digamos --reverse e --uppercase. Como programadores experientes em shell, sabemos que o tac inverte as linhas de um texto e que o tr pode converter um texto para maisculas. Assim, o cdigo para suportar estas opes novas ser to trivial quanto o do --sort.

76
$ ./usuarios-5.sh --sort | tac www uucp World Wide Web Server Unix to Unix Copy Protocol

Shell Script Profissional

unknown Unknown User tokend Token Daemon sshd root qtss

sshd Privilege separation System Administrator QuickTime Streaming Server MySQL Server Printing Services Apple Events User

postfix Postfix User mysql lp mailman Mailman user jabber Jabber User eppc

daemon System Services amavisd Amavisd User

$ ./usuarios-5.sh --sort | tac | tr a-z A-Z WWW UUCP WORLD WIDE WEB SERVER UNIX TO UNIX COPY PROTOCOL

UNKNOWN UNKNOWN USER TOKEND TOKEN DAEMON SSHD ROOT QTSS

SSHD PRIVILEGE SEPARATION SYSTEM ADMINISTRATOR QUICKTIME STREAMING SERVER MYSQL SERVER PRINTING SERVICES APPLE EVENTS USER

POSTFIX POSTFIX USER MYSQL LP MAILMAN MAILMAN USER JABBER JABBER USER EPPC

DAEMON SYSTEM SERVICES AMAVISD AMAVISD USER $

Ah, como bom programar em shell e ter mo todas estas ferramentas j prontas que fazem todo o trabalho sujo, no mesmo? Um nico detalhe que est faltando no cdigo de nosso programa que ele sempre verifica o valor de $1, no estando pronto para receber mltiplas opes. E agora ele precisar disso, pois o usurio pode querer usar todas ao mesmo tempo:
usuarios.sh --sort --reverse --uppercase

Captulo 4 Opes de linha de comando (-f, --foo)

77

Volte algumas pginas e analise o cdigo-fonte do programa. O que precisa ser feito para que $2, $3 e outros tambm sejam interpretados pelo case? Como processar um nmero varivel de opes de linha de comando? O segredo da resposta est no comando shift. Ele remove o $1, fazendo com que todos os parmetros posicionais andem uma posio na fila. Assim o $2 vira $1, o $3 vira $2, e assim por diante. Imagine uma fila de banco onde voc o quinto ($5) cliente. Quando o primeiro da fila for atendido, a fila anda (shift) e voc ser o quarto ($4). Depois o terceiro, e assim por diante, at voc ser o primeiro ($1) para finalmente ser atendido. Essa a maneira shell de fazer loop nos parmetros posicionais: somente o primeiro da fila atendido, enquanto os outros esperam. Em outras palavras: lidaremos sempre com o $1, usando o shift para fazer a fila andar.

$5

$4

$3

$2

$1

Caixa

SHIFT

$4

$3

$2

$1

Caixa

Funcionamento do comando shift


Traduzindo este comportamento para cdigos, teremos um loop while que monitorar o valor de $1. Enquanto esta varivel no for nula, temos opes de linha de comando para processar. Dentro do loop fica o case que j conhecemos. Ele j sabe consultar o valor de $1 e tomar suas aes. Ele como se fosse o caixa do banco. Uma vez processada a opo da vez, ela liberada para que a fila ande (shift) e o loop continue at no haver mais o $1:
# Tratamento das opes de linha de comando while test -n "$1" do

78
case "$1" in

Shell Script Profissional

-s | --sort) ;; esac

ordenar=1

... # Opo $1 j processada, a fila deve andar

shift done

Veja como ficou a verso nova do cdigo com este loop implementado, bem como as opes novas --reverse e --uppercase. Perceba tambm que a tela de ajuda mudou um pouco, usando o formato [OPES] para simplificar a sintaxe de uso, indicando que todas as opes seguintes no so obrigatrias (colchetes). Outra mudana dentro do case, foi a retirada do teste de existncia do parmetro $1, que era feito na opo padro (asterisco). Ele no mais necessrio visto que o while j est fazendo esta verificao.

usuarios.sh (v6)
#!/bin/bash # # usuarios.sh # Mostra os logins e nomes de usurios do sistema # Obs.: L dados do arquivo /etc/passwd #

# Verso 1: Mostra usurios e nomes separados por TAB # Verso 2: Adicionado suporte opo -h # Verso 3: Adicionado suporte opo -V e opes invlidas # Verso 4: Arrumado bug quando no tem opes, basename no # # adicionadas opes --help e --version nome do programa, -V extraindo direto dos cabealhos,

# Verso 6: Adicionadas opes -r, --reverse, -u, --uppercase, #


# #

# Verso 5: Adicionadas opes -s e --sort

leitura de mltiplas opes (loop)

# Aurlio, Novembro de 2007 ordenar=0 # A sada dever ser ordenada?

inverter=0 maiusculas=0

# A sada dever ser invertida? # A sada dever ser em maisculas?

Captulo 4 Opes de linha de comando (-f, --foo)


MENSAGEM_USO=" Uso: $(basename "$0") [OPES]

79

OPES: -r, --reverse


-s, --sort

Inverte a listagem Mostra a listagem em MAISCULAS


Mostra esta tela de ajuda e sai Ordena a listagem alfabeticamente

-u, --uppercase

-h, --help "

-V, --version

Mostra a verso do programa e sai

while test -n "$1" do


case "$1" in

# Tratamento das opes de linha de comando

-s | --sort) ;;

ordenar=1

-r | --reverse)

inverter=1 ;; -u | --uppercase) maiusculas=1 ;;


-h | --help) exit 0

echo "$MENSAGEM_USO"

;;

-V | --version)

echo -n $(basename "$0")

# Extrai a verso diretamente dos cabealhos do programa exit 0

grep '^# Verso ' "$0" | tail -1 | cut -d : -f 1 | tr -d \#

;;

80
*) esac echo Opo invlida: $1 exit 1

Shell Script Profissional

;;

# Opo $1 j processada, a fila deve andar

shift done
# Extrai a listagem

lista=$(cut -d : -f 1,5 /etc/passwd) # Ordena a listagem (se necessrio) if test "$ordenar" = 1 then fi

lista=$(echo "$lista" | sort)

# Inverte a listagem (se necessrio) if test "$inverter" = 1 then lista=$(echo "$lista" | tac) fi # Converte para maisculas (se necessrio) if test "$maiusculas" = 1 then lista=$(echo "$lista" | tr a-z A-Z) fi
# Mostra o resultado para o usurio echo "$lista" | tr : \\t

Antes de passar para a prxima opo a ser includa, cabe aqui uma otimizao de cdigo que melhorar a sua legibilidade. Alteraremos dois trechos, sem incluir nenhuma funcionalidade nova, apenas o cdigo ser reformatado para ficar mais compacto e, ao mesmo tempo, mais legvel. Isso no acontece sempre, geralmente compactar significa tornar ruim de ler. Mas como neste caso as linhas so praticamente iguais, mudando apenas uma ou outra palavra, o alinhamento beneficia a leitura:

Captulo 4 Opes de linha de comando (-f, --foo)


Cdigo atual
case "$1" in

81
Cdigo compacto

-s | --sort) ;;

ordenar=1

case "$1" in # Opes que ligam/desligam chaves ) ordenar=1 ;; ;;

-r | --reverse) ;; inverter=1

-s | --sort

-r | --reverse ) inverter=1

-u | --uppercase) ;; ... esac maiusculas=1

-u | --uppercase) maiusculas=1 ;; ... esac

# Ordena a listagem (se necessrio) if test "$ordenar" = 1 then fi

lista=$(echo "$lista" | sort)

# Inverte a listagem (se necessrio) if test "$inverter" = 1 then fi

# Ordena, inverte ou converte para maisculas (se necessrio) test "$ordenar" test "$inverter" = 1 && lista=$(echo "$lista" | sort) = 1 && lista=$(echo "$lista" | tac)

lista=$(echo "$lista" | tac)

test "$maiusculas" = 1 && lista=$(echo "$lista" | tr a-z A-Z)

# Converte para maisculas (se nece... if test "$maiusculas" = 1 then fi

lista=$(echo "$lista" | tr a-z A-Z)

Adicionando opes com argumentos


At agora vimos opes que funcionam como chaves que ligam funcionalidades. Elas no possuem argumentos, sua presena basta para indicar que tal funcionalidade deve ser ligada (ou em alguns casos, desligada).

82

Shell Script Profissional

A ltima opo que adicionaremos ao nosso programa ser diferente. Ela se chamar --delimiter, e assim como no cut, esta opo indicar qual caractere ser usado como delimitador. Em nosso contexto, isso se aplica ao separador entre o login e o nome completo do usurio, que hoje est fixo no TAB. Veja a diferena na sada do programa, com esta opo nova:
$ ./usuarios-7.sh | grep root root $ ./usuarios-7.sh --delimiter , | grep root root,System Administrator $ System Administrator

Assim o usurio ganha flexibilidade no formato de sada, podendo adaptar o texto ao seu gosto. A implementao da funcionalidade tranquila, basta usar uma nova varivel que guardar o delimitador. Mas e dentro do case, como fazer para obter o argumento da opo?
# Tratamento das opes de linha de comando while test -n "$1" do case "$1" in

-d | --delimiter)

shift ;;

delim="$1"

... esac # Opo $1 j processada, a fila deve andar

shift done

Primeiro feito um shift manual para que a opo -d (ou --delimiter) seja descartada, fazendo a fila andar e assim seu argumento fica sendo o $1, que ento salvo em $delim. Simples, no? Sempre que tiver uma opo que tenha argumentos, use esta tcnica. Ah, mas e se o usurio no passou nenhum argumento, como em usuarios.sh -d?
# Tratamento das opes de linha de comando while test -n "$1" do case "$1" in

-d | --delimiter)

Captulo 4 Opes de linha de comando (-f, --foo)


shift delim="$1" if test -z "$delim" echo "Faltou o argumento para a -d"

83

then fi ;;
... esac # Opo $1 j processada, a fila deve andar

exit 1

shift done

Agora sim, a exceo foi tratada e o usurio informado sobre seu erro. Chega, n? Quase 100 linhas est bom demais para um programa que inicialmente tinha apenas 10. Mas, em compensao, agora ele possui 12 opes novas (seis curtas e suas alternativas longas) e muita flexibilidade de modificao de sua execuo, alm de estar mais amigvel com o usurio por ter uma tela de ajuda. Veja como ficou a ltima verso:

usuarios.sh (v7)
#!/bin/bash # # usuarios.sh # Mostra os logins e nomes de usurios do sistema # Obs.: L dados do arquivo /etc/passwd #

# Verso 1: Mostra usurios e nomes separados por TAB # Verso 2: Adicionado suporte opo -h # Verso 3: Adicionado suporte opo -V e opes invlidas # Verso 4: Arrumado bug quando no tem opes, basename no # # adicionadas opes --help e --version nome do programa, -V extraindo direto dos cabealhos,

# Verso 5: Adicionadas opes -s e --sort # #

# Verso 6: Adicionadas opes -r, --reverse, -u, --uppercase, # Verso 7: Melhorias no cdigo para que fique mais legvel, adicionadas opes -d e --delimiter leitura de mltiplas opes (loop)

84
# # # Aurlio, Novembro de 2007

Shell Script Profissional

ordenar=0

inverter=0 delim='\t'

# A sada dever ser ordenada?

maiusculas=0

# A sada dever ser invertida?

# A sada dever ser em maisculas?

# Caractere usado como delimitador de sada

MENSAGEM_USO="

Uso: $(basename "$0") [OPES] OPES:

-d, --delimiter C Usa o caractere C como delimitador -r, --reverse -s, --sort Inverte a listagem Ordena a listagem alfabeticamente Mostra a listagem em MAISCULAS

-u, --uppercase -h, --help

"

-V, --version

Mostra esta tela de ajuda e sai

Mostra a verso do programa e sai

# Tratamento das opes de linha de comando while test -n "$1" do case "$1" in # Opes que ligam/desligam chaves ) ordenar=1 ;; ;;

-s | --sort

-r | --reverse ) inverter=1

-u | --uppercase) maiusculas=1 ;; -d | --delimiter) shift delim="$1" if test -z "$delim" echo "Faltou o argumento para a -d" exit 1

then ;;

fi

Captulo 4 Opes de linha de comando (-f, --foo)


-h | --help) exit 0 echo "$MENSAGEM_USO"

85

;;

-V | --version)

echo -n $(basename "$0")

# Extrai a verso diretamente dos cabealhos do programa exit 0

grep '^# Verso ' "$0" | tail -1 | cut -d : -f 1 | tr -d \#

;; *) esac

echo Opo invlida: $1 exit 1

;;

shift done

# Opo $1 j processada, a fila deve andar

# Extrai a listagem

lista=$(cut -d : -f 1,5 /etc/passwd) # Ordena, inverte ou converte para maisculas (se necessrio) test "$ordenar" test "$inverter" = 1 && lista=$(echo "$lista" | sort) = 1 && lista=$(echo "$lista" | tac)

test "$maiusculas" = 1 && lista=$(echo "$lista" | tr a-z A-Z) # Mostra o resultado para o usurio echo "$lista" | tr : "$delim"

Ufa, como cresceu! Voc pode usar este programa como modelo para seus prprios programas que tero opes de linha de comando, toda essa estrutura reaproveitvel. Para fechar de vez com chave de ouro, agora faremos o teste final de funcionamento, usando todas as opes, inclusive misturando-as. Espero que no aparea nenhum bug :)
$ ./usuarios-7.sh --help Uso: usuarios-7.sh [OPES]

86
OPES: -d, --delimiter C Usa o caractere C como delimitador -r, --reverse -s, --sort Inverte a listagem Ordena a listagem alfabeticamente Mostra a listagem em MAISCULAS Mostra esta tela de ajuda e sai

Shell Script Profissional

-u, --uppercase -h, --help -V, --version

Mostra a verso do programa e sai

$ ./usuarios-7.sh -V $ ./usuarios-7.sh root uucp lp

usuarios-7.sh Verso 7 System Administrator Unix to Unix Copy Protocol Printing Services

daemon System Services

postfix Postfix User www eppc sshd qtss

World Wide Web Server Apple Events User MySQL Server

mysql

sshd Privilege separation

mailman Mailman user amavisd Amavisd User jabber Jabber User tokend Token Daemon unknown Unknown User amavisd Amavisd User eppc lp

QuickTime Streaming Server

$ ./usuarios-7.sh --sort daemon System Services jabber Jabber User

Apple Events User Printing Services MySQL Server QuickTime Streaming Server System Administrator sshd Privilege separation

mailman Mailman user mysql qtss root sshd postfix Postfix User

tokend Token Daemon unknown Unknown User uucp www

Unix to Unix Copy Protocol World Wide Web Server

Captulo 4 Opes de linha de comando (-f, --foo)


$ ./usuarios-7.sh --sort --reverse www uucp World Wide Web Server Unix to Unix Copy Protocol

87

unknown Unknown User tokend Token Daemon sshd root qtss

sshd Privilege separation System Administrator QuickTime Streaming Server MySQL Server Printing Services Apple Events User

postfix Postfix User mysql lp mailman Mailman user jabber Jabber User eppc

daemon System Services amavisd Amavisd User WWW

$ ./usuarios-7.sh --sort --reverse --uppercase UUCP WORLD WIDE WEB SERVER UNIX TO UNIX COPY PROTOCOL

UNKNOWN UNKNOWN USER TOKEND TOKEN DAEMON SSHD ROOT QTSS

SSHD PRIVILEGE SEPARATION SYSTEM ADMINISTRATOR QUICKTIME STREAMING SERVER MYSQL SERVER PRINTING SERVICES APPLE EVENTS USER

POSTFIX POSTFIX USER MYSQL LP MAILMAN MAILMAN USER JABBER JABBER USER EPPC

DAEMON SYSTEM SERVICES AMAVISD AMAVISD USER amavisd,Amavisd User

$ ./usuarios-7.sh -s -d , daemon,System Services eppc,Apple Events User jabber,Jabber User lp,Printing Services mailman,Mailman user mysql,MySQL Server postfix,Postfix User

88
qtss,QuickTime Streaming Server root,System Administrator tokend,Token Daemon sshd,sshd Privilege separation unknown,Unknown User

Shell Script Profissional

uucp,Unix to Unix Copy Protocol www,World Wide Web Server $

Como (e quando) usar o getopts


Nas pginas anteriores vimos em detalhes como processar opes e argumentos informados pelo usurio na linha de comando. Vimos tambm que no existe um padro, mas o uso da maioria indica um padro estabelecido na prtica. Tentando preencher estas duas lacunas (padronizao e processamento), o Bash criou um comando interno (builtin) chamado de getopts. O comando getopts serve para processar opes de linha de comando. Ele toma conta do procedimento de identificar e extrair cada opo informada, alm de fazer as duas verificaes bsicas de erro: quando o usurio digita uma opo invlida e quando uma opo vem sem um argumento obrigatrio. Por ser rpido e lidar com a parte mais chata da tarefa de lidar com opes, o getopts pode ser uma boa escolha para programas simples que desejam usar apenas opes curtas. Porm, a falta de suporte s --opes-longas sua maior limitao, impossibilitando seu uso em programas que precisam delas.

Prs e contras do getopts


getopts Funciona somente no Bash Somente opes curtas (-h) Verificao automtica de argumento nulo As opes vlidas devem ser registradas em dois lugares Loop nas opes feito automaticamente na mo Funciona em qualquer shell Opes curtas e longas (-h, --help) Verificao de argumentos para opes feita manualmente As opes vlidas so registradas somente em um lugar Loop nas opes feito manualmente com o shift

Opes curtas juntas ou separadas (-abc e -a -b -c) Opes curtas somente separadas (-a -b -c)

Captulo 4 Opes de linha de comando (-f, --foo)


Se voc j tem um programa e quer incluir rapidamente o suporte a algumas opes de linha de comando, comece com o getopts e opes curtas que a soluo mais rpida. Depois se voc julgar importante incluir tambm as opes longas, mude para a soluo caseira.

89

O getopts um comando diferente, que leva um certo tempo para acostumar-se com os seus detalhes. Para comear, veremos um exemplo com muitos comentrios, para que voc tenha uma viso geral de seu uso. Leia com ateno:

getopts-teste.sh
#!/bin/bash # # getopts-teste.sh # Aurlio, Novembro de 2007 # Loop que processa todas as opes da linha de comando. # Ateno ao formato das opes vlidas ":sa:" # - Os dois-pontos do incio ligam o modo silencioso # - As opes vlidas so 'sa:', que so -s e -a # # - Os dois-pontos de 'a:' representam um argumento: -a FOO while getopts ":sa:" opcao do # $opcao guarda a opo da vez (ou ? e : em caso de erro) # $OPTARG guarda o argumento da opo (se houver) case $opcao in

done

\?) echo "ERRO Opo invlida: $OPTARG";;

a) echo "OK Opo com argumento (-a), recebeu: $OPTARG";; :) echo "ERRO Faltou argumento para: $OPTARG";;

s) echo "OK Opo simples (-s)";;

esac

# O loop termina quando nenhuma opo for encontrada.

# Mas ainda podem existir argumentos, como um nome de arquivo. # A varivel $OPTIND guarda o ndice do resto da linha de # comando, til para arrancar as opes j processadas. # echo

shift $((OPTIND - 1)) echo "RESTO: $*" echo

echo "INDICE: $OPTIND"

90

Shell Script Profissional

Antes de entrarmos nos detalhes do cdigo, veja alguns exemplos de sua execuo, que ajudaro a compreender o que o getopts faz. No primeiro exemplo foram passadas duas opes (uma simples e outra com argumento) e um argumento solto, que um nome de arquivo qualquer. No segundo exemplo temos apenas o nome de arquivo, sem opes. Por fim, no terceiro fizemos tudo errado :)
$ ./getopts-teste.sh -a FOO -s /tmp/arquivo.txt OK Opo com argumento (-a), recebeu: FOO OK Opo simples (-s) INDICE: 4

RESTO: /tmp/arquivo.txt $ ./getopts-teste.sh /tmp/arquivo.txt INDICE: 1

RESTO: /tmp/arquivo.txt $ ./getopts-teste.sh -z -a ERRO Opo invlida: z ERRO Faltou argumento para: a INDICE: 3 RESTO: $

Entendeu? Funcionar ele funciona, mas tem uma srie de detalhes que so importantes e devem ser respeitados. Estes so os passos necessrio para usar o getopts corretamente:

Colocar o getopts na chamada de um while, para que ele funcione em um loop. O primeiro argumento para o getopts uma string que lista todas as opes vlidas para o programa:

Se o primeiro caractere desta string for dois-pontos : , o getopts funcionar em modo silencioso. Neste modo, as mensagens de erro no so mostradas na tela. Isso muito til para ns, pois elas so em ingls. Outra vantagem deste modo no tratamento de erros, possibilitando diferenciar quando uma opo invlida e quando falta um argumento. Use sempre este modo silencioso.

Captulo 4 Opes de linha de comando (-f, --foo)


91

Liste as letras todas grudadas, sem espaos em branco. Lembre-se que o getopts s entende opes curtas, de uma letra. Por exemplo, hVs para permitir as opes -h, -V e -s. Se uma opo requer argumento (como a -d do nosso usuarios.sh), coloque dois-pontos logo aps. Por exempo, em hVd:sru somente a opo -d precisa receber argumento.

O segundo argumento do getopts o nome da varivel na qual ele ir guardar o nome da opo atual, til para usar no case. Aqui voc pode usar o nome que preferir. Dentro do case, liste todas as opes vlidas. Note que no necessrio o hfen! Se a opo requer um argumento, ele estar guardado na varivel $OPTARG. Trate tambm as duas alternativas especiais que o getopts usa em caso de erro:

Uma interrogao utilizada quando o usurio digita uma opo invlida. A opo digitada estar dentro de $OPTARG. Os dois-pontos so utilizados quando faltou informar um argumento obrigatrio para uma opo. A opo em questo estar dentro de $OPTARG.

Nenhum shift necessrio, pois o getopts cuida de todo o processo de avaliao

de cada opo da linha de comando.

Por fim, se processadas todas as opes e ainda sobrar algum argumento, ele tambm pode ser obtido. Isso geralmente acontece quando o aplicativo aceita receber um nome de arquivo como ltimo argumento, assim como o grep. A varivel $OPTIND guarda o ndice com a posio deste argumento. Como podem ser vrios argumentos restantes, o mais fcil usar um shift N para apagar todas as opes, sobrando apenas os argumentos. Onde N = $OPTIND - 1.

Como voc percebeu, so vrios detalhes, o getopts no um comando trivial. Mas voc se acostuma. E usado uma vez, voc pode aproveitar a mesma estrutura para outro programa. Analise o exemplo que acabamos de ver, digite-o, faa seus testes. O getopts facilita sua vida, mas primeiro voc deve ficar mais ntimo dele. Segue uma tabelinha bacana que tenta resumir todos estes detalhes:

92 Resumo das regras do getopts


while getopts OPES VAR; do ... done

Shell Script Profissional

OPES (string definida pelo programador)


a a: : a-z ? : $OPTERR $OPTARG $OPTIND

Opo simples: -a Opo com argumento: -a FOO Liga modo silencioso (se for o primeiro caractere)

$VAR (gravada automaticamente em cada lao do loop) Opo que est sendo processada agora Opo invlida ou falta argumento (modo normal) Opo invlida (modo silencioso) Falta argumento (modo silencioso) Liga/desliga as mensagens de erro (o padro OPTERR=1) Guarda o argumento da opo atual (se houver) Guarda o ndice do resto da linha de comando (no-opes)

Variveis de ambiente

O usuarios.sh, que estudamos bastante, pode ser modificado para funcionar usando o getopts. A desvantagem que somente as opes curtas podem ser utilizadas. O interessante aqui ver o diff das duas verses, para ficar bem fcil enxergar as diferenas das duas tcnicas:

usuarios.sh (diff da verso 7 para usar getopts)


--- usuarios-7.sh 2007-11-20 18:19:22.000000000 -0200 @@ -1,92 +1,88 @@ #!/bin/bash # # usuarios.sh # Mostra os logins e nomes de usurios do sistema # Obs.: L dados do arquivo /etc/passwd # +++ usuarios-7-getopts.sh 2007-11-21 00:23:18.000000000 -0200

# Verso 1: Mostra usurios e nomes separados por TAB # Verso 2: Adicionado suporte opo -h # Verso 3: Adicionado suporte opo -V e opes invlidas # Verso 4: Arrumado bug quando no tem opes, basename no # # adicionadas opes --help e --version nome do programa, -V extraindo direto dos cabealhos,

# Verso 5: Adicionadas opes -s e --sort

# Verso 6: Adicionadas opes -r, --reverse, -u, --uppercase,

Captulo 4 Opes de linha de comando (-f, --foo)


# # # # # Verso 7: Melhorias no cdigo para que fique mais legvel, leitura de mltiplas opes (loop)

93

+# Verso 7g: Modificada para usar o getopts


# Aurlio, Novembro de 2007

adicionadas opes -d e --delimiter

ordenar=0

inverter=0 delim='\t'

# A sada dever ser ordenada?

maiusculas=0

# A sada dever ser invertida?

# A sada dever ser em maisculas?

# Caractere usado como delimitador de sada

MENSAGEM_USO="

Uso: $(basename "$0") [OPES] OPES:

- -d, --delimiter C Usa o caractere C como delimitador - -r, --reverse - -s, --sort Inverte a listagem Ordena a listagem alfabeticamente Mostra a listagem em MAISCULAS

+ -d C + -r + -s + -u

- -u, --uppercase

Usa o caractere C como delimitador Inverte a listagem Ordena a listagem alfabeticamente Mostra a listagem em MAISCULAS

- -h, --help

+ -h + -V
"

- -V, --version

Mostra esta tela de ajuda e sai

Mostra esta tela de ajuda e sai

Mostra a verso do programa e sai

Mostra a verso do programa e sai

-while test -n "$1"


do

# Tratamento das opes de linha de comando

+while getopts ":hVd:rsu" opcao


- case "$1" in

+ case "$opcao" in
# Opes que ligam/desligam chaves

- -s | --sort

) ordenar=1

;;

94
- -r | --reverse ) inverter=1 - - -u | --uppercase) maiusculas=1 ;; - -d | --delimiter) - shift - - - delim="$1" if test -z "$delim" echo "Faltou o argumento para a -d" exit 1 ;;

Shell Script Profissional

- then - -

- fi

s) ordenar=1 r) inverter=1

;; ;;

+ + +

u) maiusculas=1 ;;

+ d) + delim="$OPTARG"
;;

- -h | --help)

+ h)
;;

echo "$MENSAGEM_USO" exit 0

- -V | --version)

+ V)

echo -n $(basename "$0")

# Extrai a verso diretamente dos cabealhos do programa exit 0

grep '^# Verso ' "$0" | tail -1 | cut -d : -f 1 | tr -d \#

;;

- *)

+ \?) + + + ;; +

echo Opo invlida: $1

echo Opo invlida: $OPTARG exit 1

Captulo 4 Opes de linha de comando (-f, --foo)


+ :) +
- esac ;;

95

echo Faltou argumento para: $OPTARG


exit 1

- # Opo $1 j processada, a fila deve andar - shift done

# Extrai a listagem

lista=$(cut -d : -f 1,5 /etc/passwd) # Ordena, inverte ou converte para maisculas (se necessrio) test "$ordenar" test "$inverter" = 1 && lista=$(echo "$lista" | sort) = 1 && lista=$(echo "$lista" | tac)

test "$maiusculas" = 1 && lista=$(echo "$lista" | tr a-z A-Z) # Mostra o resultado para o usurio echo "$lista" | tr : "$delim"

A mudana mais marcante a eliminao do cdigo de verificao do argumento para a opo -d. Ele j est pronto para ser usado em $OPTARG. Caso o argumento no tenha sido informado, o case cair direto na alternativa : , que mostra uma mensagem de erro genrica. O uso do shift tambm foi abolido. Fora isso, so apenas mudanas bem pequenas. Para finalizarmos este captulo, um exemplo da execuo desta verso modificada, com a limitao de no poder usar opes longas, porm com a vantagem de poder juntar vrias opes curtas em um mesmo argumento -sru.
$ ./usuarios-7-getopts.sh -h Uso: usuarios-7-getopts.sh [OPES] OPES: -d C -r -s -u -h -V

Usa o caractere C como delimitador Inverte a listagem Ordena a listagem alfabeticamente Mostra a listagem em MAISCULAS Mostra esta tela de ajuda e sai

Mostra a verso do programa e sai

96
$ ./usuarios-7-getopts.sh -V usuarios-7-getopts.sh Verso 7g WWW,WORLD WIDE WEB SERVER UNKNOWN,UNKNOWN USER TOKEND,TOKEN DAEMON

Shell Script Profissional

$ ./usuarios-7-getopts.sh -sru -d ,

UUCP,UNIX TO UNIX COPY PROTOCOL

SSHD,SSHD PRIVILEGE SEPARATION ROOT,SYSTEM ADMINISTRATOR POSTFIX,POSTFIX USER MYSQL,MYSQL SERVER MAILMAN,MAILMAN USER LP,PRINTING SERVICES JABBER,JABBER USER EPPC,APPLE EVENTS USER DAEMON,SYSTEM SERVICES AMAVISD,AMAVISD USER $ QTSS,QUICKTIME STREAMING SERVER

Das könnte Ihnen auch gefallen