Alguns recursos do BASH para você utilizar em seus programas

Neste artigo mostrarei alguns recursos úteis para os amantes de shell-script, mas que não querem escrever somente scripts, e sim verdadeiros e complexos programas.

[ Hits: 43.249 ]

Por: Leandro Santiago em 07/02/2008 | Blog: http://leandrosan.wordpress.com


Como criar programas com opções - melhorado



Numa dica anterior eu havia apresentado uma solução para criar programas que recebessem parâmetros na linha-de-comando, e que soubessem interpretar os valores recebidos.

O link para o texto original é:
Agora mostro o antigo método, mas melhorado. Agora ele é capaz de resolver parâmetros que utilizam mais de um argumento.

Mostro aqui um script de exemplo, que utiliza o método q criei:

#!/bin/bash

# Um exemplo de tratamento de parâmetros
# recebidos pela linha-de-comando no bash
#
# Por Leandro Santiago da Silva
# leandrosansilva [ ARROBA ] gmail [PONTO] com

# Inicializo variáveis booleanas

PARAMD=false
PARAME=false

# Crio o vetor que guarda todos os parâmetros
j=0
for i
{
    PARAMET[$((j++))]="$i"
}    

# Índice verificado
INDICE=0
# Indica se devo parar a verificação
FIMCADEIA=false
# A quantidade de parâmetros que devo verificar (poderia pegar o tamanho do vetor, mas assim também funciona)
QtdPar=$#
# Enquanto houver parâmetros para verificar
# e não estiver acabado a lista
while (( INDICE<QtdPar )) && ( ! $FIMCADEIA )
do
    case ${PARAMET[$INDICE]} in # Opções que necessitam de argumentos -A|--param-a|-B|--param-b|-C|--param-c)    
        # Aqui verifico parâmetros que necessitam de argumento.
        # Juntei tudo num case para facilitar a localização
        # e modificação
            if ((INDICE<QtdPar))
            then
                ((INDICE++))
                if ((${#PARAMET[((INDICE))]}>0))
                then
                        #Área 1
                        case ${PARAMET[((INDICE-1))]} in
                            -A|--param-a) PARAMA=${PARAMET[((INDICE))]}
                            ;;
                            -B|--param-b) PARAMB=${PARAMET[((INDICE))]}
                            ;;
                            -C|--param-c) PARAMC=${PARAMET[((INDICE))]}
                            ;;
                        esac
                else
                     echo ${PARAMET[((INDICE-1))]} precisa de um argumento
                    FIMCADEIA=true
                fi
            fi
        ;;

        # Parâmetros que recebem mais que um argumento
        -K|--param-k|-J|--param-j)
            INDICE_PARAM=$INDICE  
            while ((INDICE<QtdPar)) && ( ! [ "${PARAMET[((INDICE+1))]:0:1}" = "-" ] )
            do
                #Área 2
                case ${PARAMET[$INDICE_PARAM]} in
                    -K|--param-k) PARAMK="$PARAMK ${PARAMET[((INDICE+1))]}"
                    ;;

                    -J|--param-j) PARAMJ="$PARAMJ ${PARAMET[((INDICE+1))]}"
                    ;;
                esac
                ((INDICE++))
            done
        ;;

        #Área 3  
        -D|--param-d) PARAMD=true
        ;;

        -E|--param-e) PARAME=true  
        ;;

        --help) echo "Uso:
--param-a|-A opção
--param-b|-B opção
--param-c|-C opção
--param-d|-D (Ativa)
--param-e|-E (Ativa)
--param-k|-K mais de uma opção
--param-j|-J mais de uma opção"
            exit  
        ;;
        # Se o parâmetro for incorreto, paro a verificação    
        *)  echo Parâmetro inválido: ${PARAMET[((INDICE))]}
            FIMCADEIA=true
        ;;
    esac
    ((INDICE++))

    # Se passei um parâmetro errado, imediatamente finalizo o programa
    if $FIMCADEIA
    then
        exit 4 ## Porquê 4?
    fi
done

# Relatório final
echo PARAMA = $PARAMA
echo PARAMB = $PARAMB
echo PARAMC = $PARAMC

echo PARAMK = $PARAMK

echo PARAMJ = $PARAMJ

if $PARAMD
then
    echo PARAMD está ativo
else
    echo PARAMD está inativo
fi

if $PARAME
then
    echo PARAME está ativo
else
    echo PARAME está inativo
fi

Agora, explico o que cada coisa faz.

Primeiramente inicializo as variáveis booleanas. Isto é opcional, já que nem todos os programas as utilizam.

Logo após, crio um vetor com os parâmetros. Aqui, cada opção recebida pela linha-de-comando, tenha caractere espaço ou não, é colocada num elemento.

Alguns me perguntariam: "Mas porque colocar tudo num vetor?"

Eu diria: "Já vi muitas implementações de interpretação de parâmetros que manipulavam cada opção avançando com o comando shift. E confesso que não gostei nenhum pouco."

Com vetores você manipula somente índices. Modificando o valor do índice, você tem acesso à qualquer elemento do vetor na hora q bem desejar. Pode voltar ou avançar da maneira que quiser.

Isto tem um lado bom e um ruim. O bom é que é a lógica é - em minha opinião - mais fácil de ser visualizada. Outra vantagem é que você pode colocar as ações a serem executadas dentro do local correto, e não precisar se preocupar com a lógica da verificação.

Vejam como exemplo o caso dos parâmetros que recebem uma só opção. No caso do tratamento dos parâmetros '-A' e '--param-a', bastou o código PARAMA=${PARAMET[((INDICE))]} para que o próximo elemento fosse considerado um argumento dos parâmetros em questão. Não precisei analisar mais nada. Só uma linha-de-código.

Mas talvez o maior problema seja o tamanho do código. Ele pode parecer grande quando temos poucas opções à tratar, mas parece menor quando as opções são muitas.

Na "Área 1" você pode colocar os parâmetros que recebem somente um parâmetro. Como é um case, basta colocar o nome do parâmetro e o comando que deve ser executado com o argumento dele.

Em "Área 2", você coloca os parâmetros que recebem os parâmetros que podem receber um ou mais parâmetros. O engraçado é que ele elimina a utilidade da verificação acima. Mas tem o problema de utilizar um critério de parada diferente. Ele entende como "a partir daqui já outra opção" quando o próximo elemento do vetor começa com um hífen (-), enquanto que o método anterior não faz isso. Aí fica à seu critério se utiliza o primeiro ou não.

Já em "Área 3" você coloca as opções que não recebem argumento.

Agora mostro um exemplo de uso. Salvo o exemplo acima no arquivo teste-parametros.sh e dou permissão de execução à ele.

$ ./teste-parametros.sh -E --param-k um dois três -A quatro -J cinco seis -K sete oito -B nove
PARAMA = quatro
PARAMB = nove
PARAMC =
PARAMK = um dois três sete oito
PARAMJ = cinco seis
PARAMD está inativo
PARAME está ativo

Agora percebam este outro exemplo, onde não coloco argumento algum com um parâmetro que pode receber mais que um deles:

$ ./teste-parametros.sh --param-k -A 20
PARAMA = 20
PARAMB =
PARAMC =
PARAMK =
PARAMJ =
PARAMD está inativo
PARAME está inativo

E comparo com este outro:

$ ./teste-parametros.sh -A --param-j um dois três
Parâmetro inválido: um

Neste caso, vejam que "--param-j" ficou como argumento de "-A", e "um" foi reconhecido como um outro parâmetro, no caso, inválido.

Para verificar isso, executamos:

$ ./teste-parametros.sh -A --param-j
PARAMA = --param-j
PARAMB =
PARAMC =
PARAMK =
PARAMJ =
PARAMD está inativo
PARAME está inativo

O ideal é que você não coloque nenhuma ação para ser executada no momento da verificação dos parâmetros.O ideal é somente definir flags q servirão para uso do programa.

Deixo claro aqui que este exemplo, bem como o código e a lógica do script são de livre uso. Mas só peço que, se for utilizar este sistema em algum script, que cite meu nome e e-mail ;-)

Esclareço também que este código não é definitivo. Ele certamente tem falhas, e não há nada que impeça que você o melhore. Mas, se fizer isto, tente ao máximo retribuir com a comunidade mostrando as melhorias que criou.

Página anterior     Próxima página

Páginas do artigo
   1. Introdução
   2. Declaração Condicional de Funções
   3. Como criar programas internacionalizáveis
   4. Como criar programas com opções - melhorado
   5. A diferença entre as crases (``) e o '$()'
   6. Curiosidades sobre a aritmética do bash
   7. Conclusão, referências e todo o resto
Outros artigos deste autor

Recursos avançados do bash que você não aprende na escola

Ogle: O player de DVD

Mudando o tema dos cursores do mouse no Linux

Brincando com vetores

Rodando vídeos .rmvb no Linux

Leitura recomendada

Script de backup full + diferencial + compactador + restauração

BigBashView, interface gráfica para o seu shell script

Terminal Didático - Shell Script via WEB (PHP + Bootstrap)

Desinstalando todos os programas instalados a partir de uma data específica

Criando uma ISO bootável do OpenBSD

  
Comentários
[1] Comentário enviado por hugoeustaquio em 07/02/2008 - 18:03h

"Eu havia dito que expressões como ((1<2)) retornam verdadeiro para o shell. A questão é: porque isto acontece? "

Isso acontece porque 1 é menor que 2 ;)

Viva o bash!

[2] Comentário enviado por tenchi em 07/02/2008 - 20:00h

Ah hugoeustaquio, não é que você está certo? rsrs
Acabei dizendo tal bobagem para mostrar que algumas operações do bash se assemelham muito às de linguagens como C, onde 1 é verdadeiro e 0, falso. O problema é que muitas pessoas simplesmente desconhecem tais recursos.
Mas valeu pela correção ;-)

[3] Comentário enviado por chimico em 08/02/2008 - 09:53h

Muito bom, parabéns pelo artigo!

[4] Comentário enviado por k4mus em 08/02/2008 - 09:59h

Parabens amigo. A algum tempo q eu procurava algum material como este.

vlw

[5] Comentário enviado por everton3x em 08/02/2008 - 11:57h

Muito bom artigo, não tanto por mostrar como fazer "algumas coisinhas" com Shell Script, mas principalmente por mostrar aos pagãos o quão poderoso é o bash.
Parabéns!

[6] Comentário enviado por juliaojunior em 08/02/2008 - 18:26h

muito bom. Direto para favoritos. :>

[7] Comentário enviado por Teixeira em 09/02/2008 - 22:15h

Gostei muito do artigo.
Eu mesmo tinha outra idéia do assunto, pois achava que a coisa seria muito complicada.
O amigo veio provar o contrário.
Pode ser complexo no início, e exige grande disciplina, mas certamente complicado não é.
( Normalmente é a verbose que assusta um pouco )
Parabéns !

[8] Comentário enviado por elgio em 22/02/2008 - 15:24h

Sobre a arimetica um cuidado deve ser tomado ao escrever os numeros:

$ echo $(( 9 < 011 ))
0


Ué? 9 é menor que 11, então porque a expressão deu FALSO? (valor 0)

Porque números precedidos com ZERO são interpretados como octal pelo BASH e 011 octal = 9 em decimal:

$ echo $(( 9 == 011 ))
1

[9] Comentário enviado por elgio em 22/02/2008 - 15:30h

A propósito: legal este teu artigo.
Cheio de manhas.

[10] Comentário enviado por eddye00 em 28/04/2014 - 18:17h

Didática muito boa, favoritado!


Contribuir com comentário