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.

Fatiando opções com o getopts

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.

Manipulação de erros

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:

  • Encontra um parâmetro que não começa por menos (-);
  • Quando encontra um erro (como uma opção não reconhecida).

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

Sobre o autor

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.

Adicionar comentário

* Campos obrigatórios
5000
Powered by Commentics

Comentários (1)

Avatar
Novo

Cara, esses artigos da Dicas-L sempre me salvam!! Valeu muito, meus caros!!



Veja a relação completa dos artigos de Júlio Cezar Neves