Parâmetros Posicionais em Shell Script
Parâmetros Posicionais em Shell Script
Opções de linha de
comando (-f, --foo)
57
58 Shell Script Profissional
Agora, responda-me rápido: o que impede que os seus próprios programas tam-
bém tenham opções de linha de comando? Não seria interessante o seu programa
agir como qualquer outra ferramenta do sistema?
Em um sistema Unix, que possui aplicativos com mais idade que muitos leitores
deste livro, é variada a forma como que os programas esperam receber as opções de
linha de comando. Uma grande parte usa o formato de opções curtas (de uma letra)
com o hífen, mas não há um padrão. Veja alguns exemplos:
ps <letra> a u w x
Há também os aplicativos GNU, que de uma forma ou outra estão ligados à FSF
(Free Software Foundation) e preferem a licença GPL. Estes são programas com código
mais recente, que vieram para tentar substituir os aplicativos do Unix. Estão presen-
tes em todos os cantos de um sistema Linux, alguns exemplares habitam no Mac e
também podem ser instalados no Windows por intermédio do Cygwin. Eles seguem
um padrão que não é forçado, porém recomendado e adotado pela maioria.
Capítulo 4 Opções de linha de comando (-f, --foo) 59
Formato das opções de aplicativos GNU
Para pensar na cama: Os aplicativos GNU são considerados mais modernos e geralmente
possuem mais opções e funcionalidades do que seus parentes do Unix. Essa abundância
de opções pode ser benéfica ou não, dependendo do seu ponto de vista. O GNU sort
deveria mesmo ter a opção -u sendo que já existe o uniq para fazer isso? E o GNU ls com
suas mais de 80 opções, incluindo a pérola --dereference-command-line-symlink-to-
dir, não seria um exagero? Onde fica o limite do “faça apenas UMA tarefa e a faça bem-
feita”? Quantidade reflete qualidade? Evolução ou colesterol?
Algumas opções estão tão presentes por todo o sistema que é considerado um abuso
utilizá-las para outros propósitos que não os já estabelecidos. Algumas exceções são
programas antigos do Unix que já usavam estas letras para outras funções. Mas você,
como novato na vizinhança, siga as regras e faça seu programa o mais parecido com os
já existentes. O uso das seguintes opções é fortemente recomendado (caso aplicável):
Opções estabelecidas
Há outras opções que não são um padrão global, mas que são vistas em vários
aplicativos com o mesmo nome e função similar. Então, se aplicável ao seu programa,
é aconselhável que você use estes nomes em vez de inventar algo novo:
Opções recomendadas
[Link]
#!/bin/bash
# [Link]
#
# Mostra os logins e nomes de usuários do sistema
# Obs.: Lê dados do arquivo /etc/passwd
#
# Aurélio, Novembro de 2007
#
Este é um programa bem simples que mostra uma listagem dos usuários do siste-
ma, no formato “login TAB nome completo”. Seu código resume-se a uma única linha
com um cut que extrai os campos desejados do arquivo de usuários (/etc/passwd) e um
tr filtra esta saída transformando todos os dois-pontos em TABs. Veja um exemplo
de sua execução:
Capítulo 4 Opções de linha de comando (-f, --foo) 61
$ ./[Link]
root System Administrator
daemon System Services
uucp Unix to Unix Copy Protocol
lp Printing Services
postfix Postfix User
www World Wide Web Server
eppc Apple Events User
mysql MySQL Server
sshd sshd Privilege separation
qtss QuickTime Streaming Server
mailman Mailman user
amavisd Amavisd User
jabber Jabber User
tokend Token Daemon
unknown Unknown User
$
Realmente simples, não? Nas próximas páginas, esta pequena gema de uma linha
crescerá para suportar opções e aumentar um pouco a sua gama de funcionalidades.
Mas, claro, sem perder o foco inicial: mostrar a lista de usuários. Nada de ler e-mail
ou trazer um Wiki embutido ;)
cut -d : -f 2 /etc/passwd
$0 $1 $2 $3 $4 $5
Então, se nosso programa foi chamado como [Link] -h, a opção estará guardada
em $1. Aí ficou fácil. Basta conferir se o valor de $1 é -h, em caso afirmativo, mostramos
a tela de ajuda e saímos do programa. Traduzindo isso para shell fica:
[Link] (v2)
#!/bin/bash
# [Link]
#
# Mostra os logins e nomes de usuários do sistema
# Obs.: Lê dados do arquivo /etc/passwd
#
# Versão 1: Mostra usuários e nomes separados por TAB
# Versão 2: Adicionado suporte à opção -h
#
# Aurélio, Novembro de 2007
#
MENSAGEM_USO="
Uso: $0 [-h]
# Processamento
cut -d : -f 1,5 /etc/passwd | tr : \\t
Lá nos cabeçalhos deixamos claro que esta é a segunda versão do programa e que
ela traz de novidade a opção -h. A mensagem de uso é guardada em uma variável,
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 conteúdo de $1, se for o -h que queremos, mostra a
ajuda e sai com código de retorno zero, que significa: tudo certo.
$ ./[Link] -h
Na mensagem de ajuda, o [-h] entre colchetes indica que este parâmetro é opcional, ou
seja, você pode usá-lo, mas não é obrigatório.
E a cada nova opção, o if vai crescer mais. Mmmm, espera. Em vez de fazer um if
monstruoso, cheio de braços, aqui é bem melhor usar o case. De brinde ainda ganhamos
a opção “*” para pegar as opções inválidas. Veja como fica melhor e mais legível:
64 Shell Script Profissional
-V)
# mostra a versão
;;
*)
# opção inválida
;;
esac
Para a opção inválida é fácil, basta mostrar uma mensagem na tela informando
ao usuário o erro e sair do programa com código de retorno 1. Um echo e um exit são
suficientes. A versão basta mostrar uma única linha com o nome do programa e a sua
versão atual, então sai com retorno zero. Mais um echo e um exit. Parece fácil.
[Link] (v3)
#!/bin/bash
# [Link]
#
# Mostra os logins e nomes de usuários do sistema
# Obs.: Lê dados do arquivo /etc/passwd
#
# Versão 1: Mostra usuários e nomes separados por TAB
# Versão 2: Adicionado suporte à opção -h
# Versão 3: Adicionado suporte à opção -V e opções inválidas
#
# Aurélio, Novembro de 2007
#
MENSAGEM_USO="
Uso: $0 [-h | -V]
-V)
echo $0 Versão 3
exit 0
;;
*)
echo Opção inválida: $1
exit 1
;;
esac
# Processamento
cut -d : -f 1,5 /etc/passwd | tr : \\t
Mais uma vez o cabeçalho foi atualizado para informar as mudanças. Isso deve se
tornar um hábito, uma regra que não pode ser quebrada. Acostume-se desde já. Em
seguida, a mensagem de uso agora mostra que o programa também possui a opção -V.
O pipe em [-h | -V] indica que você pode usar a opção -h ou a opção -V, e ambas são
opcionais (devido aos colchetes). Depois vem o case com cada opção bem-alinhada,
tornando a leitura agradável. No -V é mostra a versão atual e a saída é normal. O as-
terisco vale para qualquer outra opção fora o -h e o -V, e além de mostrar a mensagem
informando que a opção digitada é inválida, sai com código de erro (1).
$ ./[Link] -h
$ ./[Link] -V
./[Link] Versão 3
$ ./[Link] -X
Opção inválida: -X
$ ./[Link]
Opção inválida:
$
66 Shell Script Profissional
Tudo funcionando! Ops, quase. Quando não foi passada nenhuma opção, o
programa deveria mostrar a lista de usuários normalmente, mas acabou caindo
no asterisco do case... Primeira melhoria a ser feita: só testar pela opção inválida se
houver $1; caso contrário, continue.
*)
if test -n "$1"
then
echo Opção inválida: $1
exit 1
fi
;;
Já que estamos aqui, com o -h e o -V funcionando, que tal também adicionar suas
opções equivalentes --help e --version? Vai ser muito mais fácil do que você imagina.
Lembre-se de que dentro do case é possível especificar mais de uma alternativa para
cada bloco? Então, alterando somente duas linhas do programa ganhamos de brinde
mais duas opções.
case "$1" in
-h | --help)
...
;;
-V | --version)
...
;;
*)
...
;;
esac
Capítulo 4 Opções de linha de comando (-f, --foo) 67
Uma última alteração seria melhorar este esquema de mostrar a versão do progra-
ma. Aquele número ali fixo dentro do case tende a ser esquecido. Você vai modificar o
programa, lançar outra versão e não vai se lembrar de aumentar o número da opção
-V. Por outro lado, o número da versão está sempre lá no cabeçalho, no registro das
mudanças. Será que...
Ei, isso foi legal! Além de extrair a versão do programa automaticamente, ainda nos
forçamos a sempre registrar nos cabeçalhos o que mudou na versão nova, para não
quebrar o -V. Simples e eficiente. Vejamos como ficou esta versão nova do código.
[Link] (v4)
#!/bin/bash
# [Link]
#
# Mostra os logins e nomes de usuários do sistema
# Obs.: Lê dados do arquivo /etc/passwd
#
# Versão 1: Mostra usuários e nomes separados por TAB
# Versão 2: Adicionado suporte à opção -h
# Versão 3: Adicionado suporte à opção -V e opções inválidas
# Versão 4: Arrumado bug quando não tem opções, basename no
# nome do programa, -V extraindo direto dos cabeçalhos,
# adicionadas opções --help e --version
#
# Aurélio, Novembro de 2007
#
MENSAGEM_USO="
Uso: $(basename "$0") [-h | -V]
68 Shell Script Profissional
-V | --version)
echo -n $(basename "$0")
# Extrai a versão diretamente dos cabeçalhos do programa
grep '^# Versão ' "$0" | tail -1 | cut -d : -f 1 | tr -d \#
exit 0
;;
*)
if test -n "$1"
then
echo Opção inválida: $1
exit 1
fi
;;
esac
# Processamento
cut -d : -f 1,5 /etc/passwd | tr : \\t
Agora, sim, podemos considerar que o programa ficou estável após a adição de
quatro opções de linha de comando, além do tratamento de opções desconhecidas.
Antes de continuar adicionando opções novas, é bom conferir se está mesmo tudo
em ordem com o código atual.
$ ./[Link] -h
$ ./[Link] -V
[Link] Versão 4
$ ./[Link] --version
[Link] Versão 4
$ ./[Link] --foo
Opção inválida: --foo
$ ./[Link] -X
Opção inválida: -X
$ ./[Link]
root System Administrator
daemon System Services
uucp Unix to Unix Copy Protocol
lp Printing Services
postfix Postfix User
www World Wide Web Server
eppc Apple Events User
mysql MySQL Server
sshd sshd Privilege separation
qtss QuickTime Streaming Server
mailman Mailman user
amavisd Amavisd User
jabber Jabber User
tokend Token Daemon
unknown Unknown User
$
Primeiro, o mais importante é avaliar: que tipo de opção seria útil adicionar ao
nosso programa? Como programadores talentosos e criativos que somos, podemos
adicionar qualquer coisa ao código, o céu é o limite. Mas será que uma enorme
quantidade de opções reflete qualidade? Adicionar toda e qualquer opção que vier à
mente é realmente benéfico ao programa e aos usuários? Avalie o seguinte:
■ A opção --FOO vai trazer benefícios à maioria dos usuários ou apenas a um
grupo muito seleto de usuários avançados? Estes usuários avançados já não
conseguem se virar sem esta opção?
■ A opção --FOO vai trazer muita complexidade ao código atual? Sua implemen-
tação será muito custosa ao programador?
■ A opção --FOO vai influir no funcionamento da opção --BAR já existente?
■ A opção --FOO está dentro do escopo do programa? Ela não vai descaracterizar
seu programa?
■ A opção --FOO é auto-explicável? Ou é preciso um parágrafo inteiro para des-
crever o que ela faz? Se está difícil dar um nome, pode ser um sintoma que ela
nem deveria existir em primeiro lugar...
■ A opção --FOO é realmente necessária? Não é possível fazer a mesma tarefa
usando uma combinação das opções já existentes?
■ A opção --FOO é realmente necessária? Não seria ela apenas “cosmética”, não
adicionando nenhum real valor ao seu programa?
■ A opção --FOO é REALMENTE necessária? :)
Para não ser apenas mais um personagem dessa história que se repete diariamente,
faça um favor a si mesmo: leia a lista anterior toda vez que pensar em adicionar uma
opção nova ao seu programa. Pense, analise, avalie, questione. Se a opção passar por
todas estas barreiras e provar ser realmente útil, implemente-a.
Cara chato, né? Mas se não tiver um chato para te ensinar as coisas chatas (porém
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!
Vamos decidir quais opções incluir em nosso programa. Não seremos tão rigorosos
quanto à utilidade das opções, visto que o objetivo aqui é ser didático. Mas no seu
programa, já sabe... Vejamos, o que o [Link] faz é listar os usuários do sistema.
Quais variações desta listagem poderiam ser interessantes ao usuário? Talvez uma
opção para que a listagem apareça ordenada alfabeticamente? Isso pode ser útil.
$ ./[Link] | sort
amavisd Amavisd User
daemon System Services
eppc Apple Events User
jabber Jabber User
lp Printing Services
mailman Mailman user
mysql MySQL Server
postfix Postfix User
qtss QuickTime Streaming Server
root System Administrator
sshd sshd Privilege separation
tokend Token Daemon
unknown Unknown User
uucp Unix to Unix Copy Protocol
www World Wide Web Server
$
Sabemos que existe o comando sort e que ele ordena as linhas. Mas o usuário não
é obrigado a saber disso. Ele no máximo lerá a nossa tela de ajuda e ali saberá das
possibilidades de uso do programa. Então, apesar de ter uma implementação trivial,
o usuário se beneficiará com esta opção. Nosso programa terá duas opções novas -s
e --sort que serão equivalentes e servirão para que a lista seja ordenada.
Sim, poderia ser -o e --ordena para que as opções ficassem em português. Mas como
já temos --help e --version em inglês, é preferível manter o padrão do que misturar
os idiomas. Outra opção seria deixar tudo em português alterando as opções atuais para
‑‑ajuda e --versao (sem acentos para evitar problemas!). Avalie o perfil de seus usuários
e decida qual idioma utilizar.
72 Shell Script Profissional
Para adicionar esta opção nova temos que incluí-la na mensagem de ajuda, e
também dentro do case. Alguma variável global será necessária para indicar se a saí-
da será ou não ordenada, e esta opção mudará o valor desta variável. Um esqueleto
deste esquema seria assim:
...
-s | --sort)
ordenar=1
;;
...
esac
...
if test "$ordenar" = 1
then
# ordena a listagem
fi
Este é o jeito limpo de implementar uma opção nova. Tudo o que ela faz é mudar
o valor de uma variável de nosso programa. Lá na frente o programa sabe o que fazer
com essa variável. O efeito é exatamente o mesmo de o usuário editar o programa e
mudar o valor padrão da chave (ordenar=1) no início. A vantagem em se usar uma op-
ção na linha de comando é justo evitar este passo do usuário ter que editar o código,
correndo o risco de bagunçá-lo.
Para implementar a ordenação em si, a primeira idéia que vem à cabeça é 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:
Capítulo 4 Opções de linha de comando (-f, --foo) 73
if test "$ordenar" = 1
then
cut -d : -f 1,5 /etc/passwd | tr : \\t | sort
else
cut -d : -f 1,5 /etc/passwd | tr : \\t
fi
Mas isso traz vários problemas. O primeiro é a repetição de código, o que nunca
é benéfico. Serão dois lugares para mudar caso alguma alteração precise ser feita na
dupla cut | tr. Esta solução também não irá ajudar quando precisarmos adicionar
outras opções que também manipulem a listagem. O melhor é fazer cada tarefa
isoladamente, para que a independência entre elas garanta que todas funcionem
simultaneamente: primeiro extrai a lista e guarda em uma variável. Depois ordena,
se necessário.
# Extrai a listagem
lista=$(cut -d : -f 1,5 /etc/passwd)
Assim o código fica maior, porém muito mais flexível e poderoso. A vantagem da
separação das tarefas ficará evidente quando adicionarmos mais opções ao programa.
Já são muitas mudanças, hora de lançar uma versão nova:
[Link] (v5)
#!/bin/bash
# [Link]
#
# Mostra os logins e nomes de usuários do sistema
# Obs.: Lê dados do arquivo /etc/passwd
#
# Versão 1: Mostra usuários e nomes separados por TAB
# Versão 2: Adicionado suporte à opção -h
# Versão 3: Adicionado suporte à opção -V e opções inválidas
# Versão 4: Arrumado bug quando não tem opções, basename no
# nome do programa, -V extraindo direto dos cabeçalhos,
# adicionadas opções --help e --version
74 Shell Script Profissional
MENSAGEM_USO="
Uso: $(basename "$0") [-h | -V | -s]
-s | --sort)
ordenar=1
;;
-h | --help)
echo "$MENSAGEM_USO"
exit 0
;;
-V | --version)
echo -n $(basename "$0")
# Extrai a versão diretamente dos cabeçalhos do programa
grep '^# Versão ' "$0" | tail -1 | cut -d : -f 1 | tr -d \#
exit 0
;;
*)
if test -n "$1"
then
echo Opção inválida: $1
exit 1
fi
;;
esac
Capítulo 4 Opções de linha de comando (-f, --foo) 75
# Extrai a listagem
lista=$(cut -d : -f 1,5 /etc/passwd)
O código está simples de entender, é certo que a opção nova vai funcionar. Mas
não custa testar, são apenas alguns segundos investidos. E vá que aparece algum bug
alienígena que nossos olhos terráqueos não consigam captar?
$ ./[Link] --sort
amavisd Amavisd User
daemon System Services
eppc Apple Events User
jabber Jabber User
lp Printing Services
mailman Mailman user
mysql MySQL Server
postfix Postfix User
qtss QuickTime Streaming Server
root System Administrator
sshd sshd Privilege separation
tokend Token Daemon
unknown Unknown User
uucp Unix to Unix Copy Protocol
www World Wide Web Server
$
Ufa, não foi desta vez que os ETs tiraram o nosso sono... Agora que o código do
programa está com uma boa estrutura, fica fácil adicionar duas opções novas: uma
para inverter a ordem da lista e outra para mostrar a saída em letras maiúsculas.
Digamos --reverse e --uppercase. Como programadores experientes em shell, sabe-
mos que o tac inverte as linhas de um texto e que o tr pode converter um texto para
maiúsculas. Assim, o código para suportar estas opções novas será tão trivial quanto
o do --sort.
76 Shell Script Profissional
Ah, como é bom programar em shell e ter à mão todas estas ferramentas já prontas
que fazem todo o trabalho sujo, não é mesmo? Um único detalhe que está faltando
no código de nosso programa é que ele sempre verifica o valor de $1, não estando
pronto para receber múltiplas opções. E agora ele precisará disso, pois o usuário pode
querer usar todas ao mesmo tempo:
[Link] --sort --reverse --uppercase
Capítulo 4 Opções de linha de comando (-f, --foo) 77
O segredo da resposta está no comando shift. Ele remove o $1, fazendo com que
todos os parâmetros posicionais andem uma posição 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 parâmetros 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
Traduzindo este comportamento para códigos, teremos um loop while que mo-
nitorará o valor de $1. Enquanto esta variável não for nula, temos opções de linha
de comando para processar. Dentro do loop fica o case que já conhecemos, inalte-
rado. Ele já sabe consultar o valor de $1 e tomar suas ações. Ele é como se fosse o
caixa do banco. Uma vez processada a opção da vez, ela é liberada para que a fila
ande (shift) e o loop continua até não haver mais $1:
# Tratamento das opções de linha de comando
while test -n "$1"
do
78 Shell Script Profissional
case "$1" in
-s | --sort)
ordenar=1
;;
...
esac
Veja como ficou a versão nova do código com este loop implementado, bem como
as opções novas --reverse e --uppercase. Perceba também que a tela de ajuda mudou
um pouco, usando o formato [OPÇÕES] para simplificar a sintaxe de uso, indicando que
todas as opções seguintes não são obrigatórias (colchetes). Outra mudança dentro
do case foi retirar o trecho que testava a existência do $1 dentro do asterisco, pois ela
não é mais necessária visto que o while já está fazendo esta verificação.
[Link] (v6)
#!/bin/bash
# [Link]
#
# Mostra os logins e nomes de usuários do sistema
# Obs.: Lê dados do arquivo /etc/passwd
#
# Versão 1: Mostra usuários e nomes separados por TAB
# Versão 2: Adicionado suporte à opção -h
# Versão 3: Adicionado suporte à opção -V e opções inválidas
# Versão 4: Arrumado bug quando não tem opções, basename no
# nome do programa, -V extraindo direto dos cabeçalhos,
# adicionadas opções --help e --version
# Versão 5: Adicionadas opções -s e --sort
# Versão 6: Adicionadas opções -r, --reverse, -u, --uppercase,
# leitura de múltiplas opções (loop)
#
# Aurélio, Novembro de 2007
#
OPÇÕES:
-r, --reverse Inverte a listagem
-s, --sort Ordena a listagem alfabeticamente
-u, --uppercase Mostra a listagem em MAIÚSCULAS
-s | --sort)
ordenar=1
;;
-r | --reverse)
inverter=1
;;
-u | --uppercase)
maiusculas=1
;;
-h | --help)
echo "$MENSAGEM_USO"
exit 0
;;
-V | --version)
echo -n $(basename "$0")
# Extrai a versão diretamente dos cabeçalhos do programa
grep '^# Versão ' "$0" | tail -1 | cut -d : -f 1 | tr -d \#
exit 0
;;
80 Shell Script Profissional
*)
echo Opção inválida: $1
exit 1
;;
esac
# Opção $1 já processada, a fila deve andar
shift
done
# Extrai a listagem
lista=$(cut -d : -f 1,5 /etc/passwd)
Antes de passar para a próxima opção a ser incluída, cabe aqui uma otimização de
código que melhorará a sua legibilidade. Alteraremos dois trechos, sem incluir nenhu-
ma funcionalidade nova, apenas o código será reformatado para ficar mais compacto
e, ao mesmo tempo, mais legível. Isso não acontece sempre, geralmente compactar
significa tornar ruim de ler. Mas como neste caso as linhas são praticamente iguais,
mudando apenas uma ou outra palavra, o alinhamento beneficia a leitura:
Capítulo 4 Opções de linha de comando (-f, --foo) 81
case "$1" in
-s | --sort)
ordenar=1
;; case "$1" in
...
esac
A última opção que adicionaremos ao nosso programa será diferente. Ela se cha-
mará --delimiter, e assim como no cut, esta opção indicará qual caractere será usado
como delimitador. Em nosso contexto, isso se aplica ao separador entre o login e o
nome completo do usuário, que hoje está fixo no TAB. Veja a diferença na saída do
programa, com esta opção nova:
-d | --delimiter)
shift
delim="$1"
;;
...
esac
Primeiro é feito um shift “manual” para que a opção -d (ou --delimiter) seja
descartada, fazendo a fila andar e assim seu argumento fica sendo o $1, que é então
salvo em $delim. Simples, não? Sempre que tiver uma opção que tenha argumentos,
use esta técnica. Ah, mas e se o usuário não passou nenhum argumento, como em
[Link] -d?
-d | --delimiter)
Capítulo 4 Opções de linha de comando (-f, --foo) 83
shift
delim="$1"
if test -z "$delim"
then
echo "Faltou o argumento para a -d"
exit 1
fi
;;
...
esac
Agora sim, a exceção foi tratada e o usuário informado sobre seu erro. Chega,
né? Quase 100 linhas está bom demais para um programa que inicialmente tinha
apenas 10. Mas, em compensação, agora ele possui 12 opções novas (seis curtas e
suas alternativas longas) e muita flexibilidade de modificação de sua execução, além
de estar mais amigável com o usuário por ter uma tela de ajuda. Veja como ficou a
última versão:
[Link] (v7)
#!/bin/bash
# [Link]
#
# Mostra os logins e nomes de usuários do sistema
# Obs.: Lê dados do arquivo /etc/passwd
#
# Versão 1: Mostra usuários e nomes separados por TAB
# Versão 2: Adicionado suporte à opção -h
# Versão 3: Adicionado suporte à opção -V e opções inválidas
# Versão 4: Arrumado bug quando não tem opções, basename no
# nome do programa, -V extraindo direto dos cabeçalhos,
# adicionadas opções --help e --version
# Versão 5: Adicionadas opções -s e --sort
# Versão 6: Adicionadas opções -r, --reverse, -u, --uppercase,
# leitura de múltiplas opções (loop)
# Versão 7: Melhorias no código para que fique mais legível,
# adicionadas opções -d e --delimiter
84 Shell Script Profissional
#
# Aurélio, Novembro de 2007
#
MENSAGEM_USO="
Uso: $(basename "$0") [OPÇÕES]
OPÇÕES:
-d, --delimiter=C Usa o caractere C como delimitador
-r, --reverse Inverte a listagem
-s, --sort Ordena a listagem alfabeticamente
-u, --uppercase Mostra a listagem em MAIÚSCULAS
-V | --version)
echo -n $(basename "$0")
# Extrai a versão diretamente dos cabeçalhos do programa
grep '^# Versão ' "$0" | tail -1 | cut -d : -f 1 | tr -d \#
exit 0
;;
*)
echo Opção inválida: $1
exit 1
;;
esac
# Opção $1 já processada, a fila deve andar
shift
done
# Extrai a listagem
lista=$(cut -d : -f 1,5 /etc/passwd)
Ufa, como cresceu! Você pode usar este programa como modelo para seus próprios
programas que terão opções de linha de comando, toda a estrutura é aproveitada.
Para fechar de vez com chave de ouro, agora faremos o teste final de funcionamento,
usando todas as opções, inclusive misturando-as. Espero que não apareça nenhum
bug :)
$ ./[Link] --help
OPÇÕES:
-d, --delimiter=C Usa o caractere C como delimitador
-r, --reverse Inverte a listagem
-s, --sort Ordena a listagem alfabeticamente
-u, --uppercase Mostra a listagem em MAIÚSCULAS
$ ./[Link] -V
[Link] Versão 7
$ ./[Link]
root System Administrator
daemon System Services
uucp Unix to Unix Copy Protocol
lp Printing Services
postfix Postfix User
www World Wide Web Server
eppc Apple Events User
mysql MySQL Server
sshd sshd Privilege separation
qtss QuickTime Streaming Server
mailman Mailman user
amavisd Amavisd User
jabber Jabber User
tokend Token Daemon
unknown Unknown User
$ ./[Link] --sort
amavisd Amavisd User
daemon System Services
eppc Apple Events User
jabber Jabber User
lp Printing Services
mailman Mailman user
mysql MySQL Server
postfix Postfix User
qtss QuickTime Streaming Server
root System Administrator
sshd sshd Privilege separation
tokend Token Daemon
unknown Unknown User
uucp Unix to Unix Copy Protocol
Capítulo 4 Opções de linha de comando (-f, --foo) 87
www World Wide Web Server
$ ./[Link] --sort --reverse
www World Wide Web Server
uucp Unix to Unix Copy Protocol
unknown Unknown User
tokend Token Daemon
sshd sshd Privilege separation
root System Administrator
qtss QuickTime Streaming Server
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
$ ./[Link] --sort --reverse --uppercase
WWW WORLD WIDE WEB SERVER
UUCP UNIX TO UNIX COPY PROTOCOL
UNKNOWN UNKNOWN USER
TOKEND TOKEN DAEMON
SSHD SSHD PRIVILEGE SEPARATION
ROOT SYSTEM ADMINISTRATOR
QTSS QUICKTIME STREAMING SERVER
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
$ ./[Link] -s -d ,
amavisd,Amavisd User
daemon,System Services
eppc,Apple Events User
jabber,Jabber User
lp,Printing Services
mailman,Mailman user
mysql,MySQL Server
postfix,Postfix User
88 Shell Script Profissional
O comando getopts serve para processar opções de linha de comando. Ele toma
conta do procedimento de identificar e extrair cada opção informada, além de fazer
as duas verificações básicas de erro: quando o usuário digita uma opção inválida e
quando uma opção vem sem um argumento obrigatório.
Por ser rápido e lidar com a parte mais chata da tarefa de lidar com opções, o
getopts pode ser uma boa escolha para programas simples que desejam usar apenas
opções curtas. Porém, a falta de suporte às --opções --longas é sua maior limitação,
impossibilitando seu uso em programas que precisam delas.
[Link]
#!/bin/bash
# [Link]
#
# Aurélio, Novembro de 2007
Antes de entrarmos nos detalhes do código, veja alguns exemplos de sua execução,
que ajudarão a compreender o que o getopts faz. No primeiro exemplo foram passadas
duas opções (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 opções. Por fim, no terceiro fizemos tudo errado :)
INDICE: 4
RESTO: /tmp/[Link]
$ ./[Link] /tmp/[Link]
INDICE: 1
RESTO: /tmp/[Link]
$ ./[Link] -z -a
ERRO Opção inválida: z
ERRO Faltou argumento para: a
INDICE: 3
RESTO:
Entendeu? Funcionar ele funciona, mas tem uma série de detalhes que são im-
portantes e devem ser respeitados. Estes são os passos necessário 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 opções
válidas para o programa:
■ Se o primeiro caractere desta string for dois-pontos “:”, o getopts funcionará
em modo silencioso. Neste modo, as mensagens de erro não são mostradas
na tela. Isso é muito útil para nós, pois elas são em inglês. Outra vantagem
deste modo é no tratamento de erros, possibilitando diferenciar quando é
um opção inválida e quando falta um argumento. Use sempre este modo
silencioso.
Capítulo 4 Opções de linha de comando (-f, --foo) 91
■ Liste as letras todas grudadas, sem espaços em branco. Lembre-se que o
getopts só entende opções curtas, de uma letra. Por exemplo, “hVs” para
permitir as opções -h, -V e -s.
■ Se uma opção requer argumento (como a -d do nosso [Link] que aca-
bamos de fazer), coloque dois-pontos logo após. Por exempo, em “hVd:sru”
somente a opção -d precisa receber argumento.
■ O segundo argumento do getopts é o nome da variável na qual ele irá guardar
o nome da opção atual, útil para usar no case. Aqui você pode usar o nome
que preferir.
■ Dentro do case, liste todas as opções válidas. Note que não é necessário o hífen!
Se a opção requer um argumento, ele estará guardado na variável $OPTARG. Trate
também as duas alternativas especiais que o getopts usa em caso de erro:
■ Uma interrogação é utilizada quando o usuário digita uma opção inválida.
A opção digitada estará dentro de $OPTARG.
■ Os dois-pontos são utilizados quando faltou informar um argumento obri-
gatório para uma opção. A opção em questão estará dentro de $OPTARG.
■ Nenhum shift é necessário, pois o getopts cuida de todo o processo de percorrer
cada opção da linha de comando.
■ Por fim, se processadas todas as opções e ainda sobrar algum argumento, ele
também pode ser obtido. Isso geralmente acontece quando o aplicativo aceita
receber um nome de arquivo como último argumento, assim como o grep. A
variável $OPTIND guarda o índice com a posição deste argumento. Como podem
ser vários argumentos restantes, o mais fácil é usar um shift N para apagar todas
as opções, sobrando apenas os argumentos. Onde N = $OPTIND - 1.
Como você percebeu, são vários detalhes, o getopts não é um comando trivial.
Mas acostuma. E usado uma vez, você pode aproveitar a mesma estrutura para outro
programa. Analise o exemplo que acabamos de ver, digite-o, faça 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 Shell Script Profissional
O [Link], que estudamos bastante, pode ser modificado para funcionar usan-
do o getopts. A desvantagem é que somente as opções curtas podem ser utilizadas.
O interessante aqui é ver o diff das duas versões, para ficar bem fácil enxergar as
diferenças das duas técnicas:
MENSAGEM_USO="
Uso: $(basename "$0") [OPÇÕES]
OPÇÕES:
- -d, --delimiter=C Usa o caractere C como delimitador
- -r, --reverse Inverte a listagem
- -s, --sort Ordena a listagem alfabeticamente
- -u, --uppercase Mostra a listagem em MAIÚSCULAS
+ -d C Usa o caractere C como delimitador
+ -r Inverte a listagem
+ -s Ordena a listagem alfabeticamente
+ -u Mostra a listagem em MAIÚSCULAS
- -r | --reverse ) inverter=1 ;;
- -u | --uppercase) maiusculas=1 ;;
-
- -d | --delimiter)
- shift
- delim="$1"
-
- if test -z "$delim"
- then
- echo "Faltou o argumento para a -d"
- exit 1
- 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 versão diretamente dos cabeçalhos do programa
grep '^# Versão ' "$0" | tail -1 | cut -d : -f 1 | tr -d \#
exit 0
;;
- *)
- echo Opção inválida: $1
+ \?)
+ echo Opção inválida: $OPTARG
+ exit 1
+ ;;
+
Capítulo 4 Opções de linha de comando (-f, --foo) 95
+ :)
+ echo Faltou argumento para: $OPTARG
exit 1
;;
esac
-
- # Opção $1 já processada, a fila deve andar
- shift
done
# Extrai a listagem
lista=$(cut -d : -f 1,5 /etc/passwd)
$ ./[Link] -h
OPÇÕES:
-d C Usa o caractere C como delimitador
-r Inverte a listagem
-s Ordena a listagem alfabeticamente
-u Mostra a listagem em MAIÚSCULAS
$ ./[Link] -V
[Link] Versão 7g
$ ./[Link] -sru -d ,
WWW,WORLD WIDE WEB SERVER
UUCP,UNIX TO UNIX COPY PROTOCOL
UNKNOWN,UNKNOWN USER
TOKEND,TOKEN DAEMON
SSHD,SSHD PRIVILEGE SEPARATION
ROOT,SYSTEM ADMINISTRATOR
QTSS,QUICKTIME STREAMING SERVER
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
$