Uma característica para deixar um shell-script mais robusto e menos “sequencial/batch-mode” é o tratamento de argumentos. No meu clássico tutorial Programando em Shell-Script, o tópico Variáveis Especiais nos traz os primeiros itens que devemos aprender para o tratamento de argumentos. Existem variáveis especiais que tratam os argumentos passados para um programa ou uma função. Estes são:
- $0 – Retorna o nome do script que foi executado
- $N – Onde N é um número, corresponde ao argumento passado (1 = primeiro argumento, 2 = segundo argumento, 3 = terceiro argumento, etc)
- $* – Retorna todos os argumentos de uma vez.
- $# – Retorna a quantidade de argumentos passado para o script. (argc)
#!/bin/bashif [ $# -lt 1 ]; then echo "Faltou utilizar pelo menos um argumento!" exit 1fiecho "Numero de argumentos: $#"COUNT=0for ARG in $*; do COUNT=`expr $COUNT + 1` echo "Argumento $COUNT: $ARG"done
As linhas 3 a 6 verificam se a quantidade de argumentos ($#) é menor (-lt – less than) que 1. Ou seja, se o usuário não chamou o programa com nenhum argumento, ele imprime um erro e sai do programa com status 1.
A linha 8 mostra quantos argumentos foram utilizados, usando novamente o $#.
O resto das linhas, 10 a 14, usam o $* com um laço for e um contador para mostrar quais foram os argumentos.
Executando agora este script sem argumentos:
A linha 8 mostra quantos argumentos foram utilizados, usando novamente o $#.
O resto das linhas, 10 a 14, usam o $* com um laço for e um contador para mostrar quais foram os argumentos.
Executando agora este script sem argumentos:
./tmp.shFaltou utilizar pelo menos um argumento!Agora executando com dois argumentos:./tmp.sh naosei testandoNumero de argumentos: 2Argumento 1: naoseiArgumento 2: testandoE agora com 4 argumentos:./tmp.sh a b c dNumero de argumentos: 4Argumento 1: aArgumento 2: bArgumento 3: cArgumento 4: d
Bem simples né?
Exemplo:
Argumentos como opções e seus valores
Algo comum que vemos nos programas são opções. Opções não deixam de ser argumentos para um programa, mas eles tem um significado especial. Do tipo: Se a opção -d existir, ativar durante o programa o modo de depuração. Se houver um -h, então mostre uma ajuda e não faça mais nada. Se houver um -v mostre a versão, e por aí vai.Exemplo:
#!/bin/bashcase $1 in "-h") echo "Isto seria uma ajuda... Mas fiquei com preguiça de escrevê-la." ;; "-v") echo "Versão 666." ;; *) echo "Opção inválida!" exit 1 ;;esacExemplos do uso do script:$ ./tmp.sh -hIsto seria uma ajuda... Mas fiquei com preguiça de escrevela.$ ./tmp.sh -vVersão 666.$ ./tmp.sh -OOpção inválida!$ ./tmp.shOpcao invalida!
Com isso a gente resolve um problema e cria mais outros dois…
- E se o usuário colocar as duas opções? Só uma funcionaria.
- E se uma das opções precisasse de um valor? Estilo “-f arquivo.log” gravaria um arquivo de log com as operações.
Utilizando o getopts para tratar tratar argumentos e opções
Seguindo a mesma linha de raciocínio, vamos logo para um exemplo de programa. Supondo que queiramos um shell-script que faça isso:- Caso a opção -h seja usada, mostra a ajuda e sai do programa.
- Caso a opção -v seja usada, mostra a versão e sai do programa.
- Caso a opção -o seja usada, grava um arquivo de log com as operações efetuadas e resultados.
- Caso a opção -u seja usada, mostra o resultado do comando “uname -a”
- Caso a opção -m seja usada, mostra o resultado do comando “free -m”
- Caso a opção -s seja usada, mostra o resultado do comando “swap -s”
#!/bin/bashfunction PrintUsage() { echo "Uso: `basename $0` <-umsf> [-ohv]" exit 1}while getopts "hvo:umsf" OPTIONdo case $OPTION in h) PrintUsage ;; v) echo "`basename $0` versao 666." exit ;; o) ARQUIVO_LOG=$OPTARG ;; u) DO_UNAME=1 ;; m) DO_FREE=1 ;; s) DO_SWAPON=1 ;; ?) PrintUsage ;; esacdoneshift $((OPTIND-1))if [ -z "$DO_UNAME" ] && [ -z "$DO_FREE" ] && [ -z "$DO_SWAPON" ] && [ -z "$DO_FDISK" ]; then PrintUsagefiif [ "$ARQUIVO_LOG" ]; then echo "Execucao iniciada em `date`." >> $ARQUIVO_LOG if [ "$DO_UNAME" == 1 ]; then uname -a >> $ARQUIVO_LOG fi if [ "$DO_FREE" == 1 ]; then free -m >> $ARQUIVO_LOG fi if [ "$DO_SWAPON" == 1 ]; then swapon -s >> $ARQUIVO_LOG fielse echo "Execucao iniciada em `date`." if [ "$DO_UNAME" == 1 ]; then uname -a fi if [ "$DO_FREE" == 1 ]; then free -m fi if [ "$DO_SWAPON" == 1 ]; then swapon -s fifi
O interessante para nós são as linhas 8 a 28. O laço while getopts começa a tratar todos os argumentos. A cada iteração do laço, ele coloca a letra da opção na variável $OPTION.
Note que para cada opção que precisamos, colocamos uma letra no primeiro argumento do getopts:
Note que para cada opção que precisamos, colocamos uma letra no primeiro argumento do getopts:
while getopts "hvo:umsf" OPTION
Note também que depois da letra o temos um dois pontos (:).
Esse dois pontos significa que logo após a opção -o, o usuário precisa
fornecer um valor. Este valor é automaticamente armazenado na variável $OPTARG.
Dessa maneira, podemos executar esse programa de diversas formas:
Dessa maneira, podemos executar esse programa de diversas formas:
./tmp.sh -o arquivo.log -u(executa o "uname -a" e grava no arquivo arquivo.log)./tmp.sh -um(executa os comandos "uname -a" e "free -m")./tmp.sh -m -s -u(executa os comandos "free -m", "swapon -s" e "uname -a")
Ou seja, não importa a ordem, o getopts vai reconhecer e executar as ações de acordo com a opção especificada.
E se você colocar uma opção que não está contemplatada… O “?” do case irá ser executado, por exemplo:
E se você colocar uma opção que não está contemplatada… O “?” do case irá ser executado, por exemplo:
./tmp.sh -a./tmp.sh: illegal option -- aUso: tmp.sh <-umsf> [-ohv]E dessa forma fica bem fácil de entender e usar o getopts :) Depois que o
laço é todo feito e executado em todos os argumentos (no meu caso,
preferi apenas configurar variáveis para cada opção e tratá-las depois),
ele executa o comando que está na linha 28:shift $((OPTIND-1))Este comando faz com que os argumentos de opções sejam “comidos“, até que não sobre nenhuma opção. Em outras palavras, os argumentos representados pelas variáveis $N só serão aqueles que não pertençam a nenhuma opção. Exemplo:./tmp.sh -u -o arquivo.log -m argumento1 argumento2
Nesse caso, o $1 seria o argumento1 e o $2 seria o argumento2, quando
na verdade, sem o shift, eles seriam respectivamente o $5 e $6.
Como nem tudo é perfeito, a função getopts do bash não aceita opções longas (–nome-da-opcao), ou seja, voce só pode utilizar uma letra como opção. Represente bem suas opções com as letras! :)
Como nem tudo é perfeito, a função getopts do bash não aceita opções longas (–nome-da-opcao), ou seja, voce só pode utilizar uma letra como opção. Represente bem suas opções com as letras! :)
Argumentos dentro de funções
Se dentro de um shell-script temos uma função, essa função é enxergada pela shell como se fosse um comando. Nesse sentido, dentro de uma função as variáveis $N definidas pelo programa não funcionarão. Exemplo:#!/bin/bashfunction Dummy() { echo "Numero de argumentos: $#" COUNT=0 for ARG in $*; do COUNT=`expr $COUNT + 1` echo "Argumento $COUNT: $ARG" done}DummyNão importa o que você executar com o script acima, a saída será sempre a
mesma: 0 números de argumentos, como mostrado a seguir.$ ./tmp.shNumero de argumentos: 0$ ./tmp.sh naosei temporarioNumero de argumentos: 0$ ./tmp.sh a b c d e f gNumero de argumentos: 0Para a função Dummy, as variáveis especiais dos argumentos funcionam apenas para a função e não para o programa inteiro. É como se as variáveis fossem locais, e não globais. Vamos então substituir a linha da chamada da função Dummy (linha 13) por:Dummy a b c dE tentar executar novamente:$ ./tmp.shNumero de argumentos: 4Argumento 1: aArgumento 2: bArgumento 3: cArgumento 4: dSabendo disso, não se percam na hora de usar os argumentos dentro das
funções e lembrem-se que isto pode ser útil na hora de implementar
diversas funções dentro de um script. Um bom exemplo disso é implementar
a função PrintUsage que usamos anteriormente para, além de mostrar uma
mensagem de uso, mostrar também uma mensagem de erro personalizada:function PrintUsage() { [ "$1" ] && echo -ne "Erro: $1\n" echo "Uso: $(basename $0) <-umsf> [-ohv]" exit 1}
Agora é so chamar a função como…
Use a criatividade de um programador (afinal, programação é arte) e comece a aprimorar suas ferramentas bash! :)
PrintUsage "Faltando parâmetros."PrintUsage "Opção inválida."PrintUsage "No donut for you." |