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/bash
if
[ $
# -lt 1 ]; then
echo
"Faltou utilizar pelo menos um argumento!"
exit
1
fi
echo
"Numero de argumentos: $#"
COUNT=0
for
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.sh
Faltou utilizar pelo menos um argumento!
Agora executando com dois argumentos:
./tmp.sh naosei testando
Numero de argumentos: 2
Argumento 1: naosei
Argumento 2: testando
E agora com 4 argumentos:
./tmp.sh a b c d
Numero de argumentos: 4
Argumento 1: a
Argumento 2: b
Argumento 3: c
Argumento 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/bash
case
$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
;;
esac
Exemplos do uso do script:
$ ./tmp.sh -h
Isto seria uma ajuda... Mas fiquei com preguiça de escrevela.
$ ./tmp.sh -v
Versão 666.
$ ./tmp.sh -O
Opção inválida!
$ ./tmp.sh
Opcao 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/bash
function
PrintUsage() {
echo
"Uso: `basename $0` <-umsf> [-ohv]"
exit
1
}
while
getopts
"hvo:umsf"
OPTION
do
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
;;
esac
done
shift
$((OPTIND-1))
if
[ -z
"$DO_UNAME"
] && [ -z
"$DO_FREE"
] && [ -z
"$DO_SWAPON"
] && [ -z
"$DO_FDISK"
];
then
PrintUsage
fi
if
[
"$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
fi
else
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
fi
fi
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 -- a
Uso: 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/bash
function
Dummy() {
echo
"Numero de argumentos: $#"
COUNT=0
for
ARG
in
$*;
do
COUNT=`
expr
$COUNT + 1`
echo
"Argumento $COUNT: $ARG"
done
}
Dummy
Nã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.sh
Numero de argumentos: 0
$ ./tmp.sh naosei temporario
Numero de argumentos: 0
$ ./tmp.sh a b c d e f g
Numero de argumentos: 0
Para 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 d
E tentar executar novamente:
$ ./tmp.sh
Numero de argumentos: 4
Argumento 1: a
Argumento 2: b
Argumento 3: c
Argumento 4: d
Sabendo 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." |