De acordo com as Leis 12.965/2014 e 13.709/2018, que regulam o uso da Internet e o tratamento de dados pessoais no Brasil, ao me inscrever na newsletter do portal DICAS-L, autorizo o envio de notificações por e-mail ou outros meios e declaro estar ciente e concordar com seus Termos de Uso e Política de Privacidade.
Colaboração: Júlio Cezar Neves
Data de Publicação: 17 de outubro de 2017
Analisar uma linha de comando e quebrá-la em cada uma de suas opções é uma
tarefa complicada, pois, só falando em duas opções (-A e -B) sendo que -B
pode ou não ter argumentos (ARG), teríamos de pesquisar por:
| -AB | -ABARG | -AB ARG |
| -A -B | -A -BARG | -a -B ARG |
| -BA | ||
| -B -A | -BARG -A | -B ARG -A |
Isso sem falar que, se uma das opções for facultativa, a gama da análise
(provavelmente em um comando case) seria o dobro dessa. Em virtude dessa
complexidade e para aliviar a vida do programador, foi desenvolvido o nosso
amigo getopts, que, por ser um intrínseco (builtin), é bastante veloz. Ele
foi feito para suceder a antiga implementação que chamava-se getopt (sem o s),
que não era intrínseco e tinha alguns bugs, mas seu único pecado é não aceitar
opções longas dos comandos, previstas pelo GNU (como em CMD --help, p.ex.).
Só para melhorar o entendimento unificando a terminologia que será usada, pelos
parágrafos anteriores você já deve ter reparado que chamamos de opção uma
letra precedida de um sinal de menos ou traço (-) e argumentos são os textos
requeridos por algumas opções ou simplesmente passados para a linha de comando.
Exemplo:
cut -f 2 -s -d : /etc/passwd
| Trecho | Terminologia |
|---|---|
| cut | Programa |
| -f -s -d | Opções |
| 2 | Argumento da opção -f |
| : | Argumento da opção -d |
| /etc/passwd | Argumento do programa |
Sintaxe:
getopts CADOPTS VAR [ARGS]
Onde:
CADOPTS |
contém a cadeia de opções e suas eventuais necessidades de argumento. Quando uma opção aceita um argumento, ela deverá vir seguida de um dois pontos (:). Como veremos adiante esta variável receberá um ponto de interrogação (?) em caso de erro. Assim sendo, o ponto de interrogação (?) e os dois pontos (:) são especiais para o getopts e em virtude disso esses dois caracteres não podem ser usados como opções. |
VAR |
O comando terá de ser executado (normalmente dentro de um loop de while) tantas vezes quantas foram as opções válidas. A variável VAR, receberá em cada passada a opção (tirada de CADOPTS) que será analisada, ou terá uma sinalização (flag) de erro. |
ARGS |
Normalmente são pesquisadas as opções passadas em VAR, mas se os argumentos forem passados em ARGS, eles é que serão analisados, ou seja, a existência desse parâmetro diz ao getopts para analisar o conteúdo de ARGS em vez dos parâmetros posicionais. |
Variáveis usadas pelo getopts:
| OPTARG | Contém cada argumento das opções descobertas em cada volta do loop do getopts ou uma marca de erro que analisaremos à frente; |
| OPTIND | Contém o índice do parâmetro posicional em análise que você gerenciará para saber, pelo valor de OPTIND a opção que está sendo trabalhada; |
| OPTERR | É uma variável que quando tem valor 1 indica para Shell que os erros irão para a tela e este é o padrão pois é sempre inicializada com valor 1. Com valor zero, os erros são assinalados, mas não são exibidos. |
Como já vimos o getopts pode ser executado de dois modos:
| Modo silencioso | Quando a variável do sistema $OPTERR for zero ou quando a cadeia que você passou em OPTARG começa por dois pontos (:); |
| Modo falador | Este é o normal. OPTERR é igual a um e a cadeia passada em OPTARG não começa por dois pontos (:). |
Se for achada uma opção inválida, getopts põe um ponto de interrogação (?) em
VAR. Se não estiver em modo silencioso dá uma mensagem de erro e esvazia
OPTARG. Se estiver silencioso, o caractere que gerou a opção inválida é
colocado em OPTARG.
Se um argumento requerido não for informado e getopts não estiver em modo
silencioso, um ponto de interrogação (?) é colocado em VAR, OPTARG é esvaziada
e é dada uma mensagem de erro. Caso esteja em modo silencioso, um dois pontos
(:) é colocado em VAR e OPTARG recebe o caractere da opção da qual não foi
informado o argumento requerido
Quando o assunto é programação em bash, aconselho que, em seus scripts, sempre use o modo silencioso e monitore eventuais erros.
Como esse cara funciona?
Vamos fazer essa análise por exemplos, que é muito mais simples de aprender:
Exemplo:
Suponha que um programa possa receber a opção -C. O script, para análise
das opções deveria ser do tipo:
$ cat cut1.sh
#!/bin/bash
while getopts c Opc
do
case $Opc in
c) echo Recebi a opção -c
;;
esac
done
Como vamos trabalhar diversos exemplos em cima deste mesmo script, preferi
já começá-lo com um case, quando nesse caso bastaria um if.
Vamos vê-lo funcionando:
1. Executando-o sem nenhuma opção, ele não produz nenhuma saída;
2. Usando a opção -c, que é o esperado:
$ cut1.sh -c
Recebi a opção -c
3. Usando a opção -z, que é inválida:
$ cut1.sh -z
./cut1.sh: opção ilegal -- z
Xiii, quem deu a mensagem foi o Shell e eu não soube de nada. Mesmo com erro, o programa continuaria em execução, porque o erro não é tratado pelo programa.
Então vamos alterá-lo colocando-o em modo silencioso, o que, como já vimos,
pode ser feito iniciando a cadeia de opções (CADOPTS) com um dois pontos
ou atribuindo zero à variável $OPTERR.
$ cat cut2.sh
#!/bin/bash
while getopts :c Opc
do
case $Opc in
c) echo Recebi a opção -c
;;
\?) echo Caractere $OPTARG inválido
exit 1
done
Agora coloquei um dois pontos à frente de CADOPTS e passei a monitorar um
ponto de interrogação (?) no case, porque quando é achado um caractere
inválido, o getopts, como já vimos, coloca um ponto de interrogação (?)
na variável $Opc. Desta forma, listei $OPTPARG e em seguida dei exit.
Note que antes da interrogação coloquei uma contrabarra (\), para que o
Shell não o expandisse para todos os arquivos que contém somente um caractere
no nome.
4. Agora que já tenho o ambiente sob meu controle, vamos executá-lo novamente com uma opção não prevista:
$ cut2.sh -z
Caractere z inválido
$ echo $?
1
Agora aconteceu o que queríamos: deu a nossa mensagem e o programa abortou,
passando 1 como código de retorno ($?).
Bem, já examinamos todas as possibilidades que a passagem de opções
pode ter. Vamos agora esmiuçar o caso que uma opção que tenha parâmetro
associado. Para dizer que uma opção pode ter um argumento, basta colocar um
dois pontos (:) após a letra da opção.
Então, ainda evoluindo o programa de teste que estamos fazendo vamos supor que a opção -c requeresse um argumento. Deveríamos então fazer algo como:
$ cat cut3.sh
#!/bin/bash
while getopts :c: Opc
do
case $Opc in
c) echo Recebi a opção -c
echo Parâmetro passado para a opção -c foi $OPTARG
;;
\?) echo Caractere $OPTARG inválido
exit 1
;;
:) echo -c precisa de um argumento
exit 1
esac
done
Agora introduzimos o caractere dois pontos (:) no case porque, como já
vimos, quando um parâmetro não é localizado, o getopts em modo silencioso
coloca um dois pontos (:) em $Opc, caso contrário, a falta de argumento
é sinalizada, com um ponto de interrogação (?) nesta mesma variável.
Vamos então analisar todas as possibilidades:
1. Executando-o sem nenhuma opção, ele não produz nenhuma saída;
2. Passando a opção -c acompanhada de seu parâmetro, que é o esperado:
$ cut3.sh -c 2-5
Recebi a opção -c
Parâmetro passado para a opção -c foi 2-5
3. Passando a opção correta, porém omitindo o parâmetro requerido:
$ cut3.sh -c -c precisa de um argumento $ echo $? 1
Para finalizar esta série, vejamos um caso interessante. Desde o início,
venho simulando nesse exemplo a sintaxe do comando cut com a opção
-c. Para ficar igualzinho à sintaxe do cut, só falta receber o nome
do arquivo. Vejamos como fazê-lo:
$ cat cut4.sh
#!/bin/bash
# Inicializar OPTIND é necessário caso o script tenha
#+ usado getopts antes. OPTIND mantém seu valor
OPTIND=1
while getopts :c: Opc
do
case $Opc in
c) echo Recebi a opção -c
echo Parâmetro passado para a opção -c foi $OPTARG
;;
\?) echo Caractere $OPTARG inválido
exit 1
;;
:) echo -c precisa de um argumento
exit 1
esac
shift $((--OPTIND))
Args="$@"
echo "Recebi o(s) seguinte(s) argumento(s) extra(s): $Args"
done
E executando-o vem:
$ cut4.sh -c 2-5 /caminho/do/arquivo
Recebi a opção -c
Parâmetro passado para a opção -c foi 2-5
Recebi o(s) seguinte(s) argumento(s) extra(s): /caminho/do/arquivo
Agora vamos dar um mergulho num exemplo um bem completo e analisá-lo. Para esse
script interessam somente as opções -f, -u argumento e -C. Veja
o código (mas este ainda não está 100%).
#!/bin/bash
printf "%29s%10s%10s%10s%10s\n" Comentário Passada Char OPTARG OPTIND
while getopts ":fu:C" VAR
do
case $VAR in
f) Coment="Achei a opção -f"
;;
u) Coment="Achei a opção -u $OPTARG"
;;
C) Coment="Achei a opção -C"
;;
\?) Coment="Achei uma opção invalida -$OPTARG"
;;
:) Coment="Faltou argumento da opção -u"
esac
printf "%30s%10s%10s%10s%10s\n" "$Coment" $((++i)) "$VAR" "$OPTARG" "$OPTIND"
done
Agora vejamos a sua execução passando todos as opções juntas e sem passar o
parâmetro requerido pela opção -u (repare os dois pontos (:) que seguem
o u na chamada do getopts neste exemplo).
$ getop.sh -fCxu
Comentário Passada Char OPTARG OPTIND
Achei a opção -f 1 f 1
Achei a opção -C 2 C 1
Achei uma opção invalida -x 3 ? x 1
Faltou argumento da opção -u 4 : u 2
Repare que a variável $OPTIND não foi incrementada. Vejamos então o mesmo exemplo, porém passando as opções separadas:
$ getop.sh -f -C -x -u
Comentário Passada Char OPTARG OPTIND
Achei a opção -f 1 f 2
Achei a opção -C 2 C 3
Achei uma opção invalida -x 3 ? x 4
Faltou argumento da opção -u 4 : u 5
Ah, agora sim! $OPTIND passou a ser incrementado. Para fechar, vejamos um
exemplo sem opção inválida e no qual passamos o parâmetro requerido pela
opção -u:
$ getop.sh -f -C -u Util
Comentário Passada Char OPTARG OPTIND
Achei a opção -f 1 f 2
Achei a opção -C 2 C 3
Achei a opção -u Util 3 u Util 5
Algumas observações:
1. Vimos que mesmo após encontrar erro, o getopts continuou analisando
as opções, isso se dá porque o que foi encontrado pode ser um parâmetro de
uma opção e não um erro;
2. Então como distinguir um erro de um parâmetro? Fácil: se a opção em análise requer argumento, o conteúdo de $OPTARG é ele, caso contrário é um erro;
3. Quando encontramos um erro devemos encerrar o programa pois o getopts
só aborta sua execução quando:
-);
O parâmetro especial -- marca o fim das opções, mas isso é uma convenção
para todos os comandos que interagem com o Shell.
Então a nossa versão final do programa seria:
$ cat getop.sh
#!/bin/bash
function Uso
{
echo " $Coment
Uso: $0 -f -C -u parâmetro" >&2
exit 1
}
(($#==0)) && { Coment="Faltou parâmetro"; Uso; }
printf "%29s%10s%10s%10s%10s\n" Comentário Passada Char OPTARG OPTIND
while getopts ":fu:C" VAR
do
case $VAR in
f) Coment="Achei a opção -f"
;;
u) Coment="Achei a opção -u $OPTARG"
;;
C) Coment="Achei a opção -C"
;;
\?) Coment="Achei uma opção invalida -$OPTARG"
Uso
;;
:) Coment="Faltou argumento da opção -u"
esac
printf "%30s%10s%10s%10s%10s\n" "$Coment" $((++i)) "$VAR" \
"$OPTARG" "$OPTIND"
done
Júlio Cézar Neves |
|
|---|---|
![]() |
O 4º UNIX do mundo nasceu na Cidade Maravilhosa, mais precisamente na Cobra Computadores, onde à época trabalhava o Julio. Foi paixão à 1ª vista! Desde então, (1980) atua nessa área como especialista em Sistemas Operacionais e linguagens de programação. E foi por essa afinidade que quando surgiu o Linux foi um dos primeiros a estudá-lo com profundidade e adotá-lo como Sistema Operacional e filosofia de vida. É autor dos livros Programação Shell Linux, 11ª edição e Bombando o Shell. |