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. |
This policy contains information about your privacy. By posting, you are declaring that you understand this policy:
This policy is subject to change at any time and without notice.
These terms and conditions contain rules about posting comments. By submitting a comment, you are declaring that you agree with these rules:
Failure to comply with these rules may result in being banned from submitting further comments.
These terms and conditions are subject to change at any time and without notice.
Comentários (1)
Cara, esses artigos da Dicas-L sempre me salvam!! Valeu muito, meus caros!!