Parsing de argumentos com getopts
Esta aula ensina a usar o comando getopts em shell script para fazer parsing de argumentos de linha de comando, cobrindo flags curtas, argumentos com valor, construção de mensagens de uso e exemplos práticos. O conteúdo é voltado para alunos intermediários e inclui exercícios com respostas colapsáveis.
Quando escrevemos scripts em Bash, uma das tarefas mais comuns é interpretar os argumentos passados pelo usuário na linha de comando. Sem uma ferramenta adequada, isso pode se tornar um código confuso e propenso a erros, especialmente quando temos muitas opções. É aqui que entra o getopts, um utilitário nativo do Bash que facilita o parsing de opções curtas (como -v ou -f arquivo). Nesta aula, vamos explorar como usar o getopts para criar scripts mais robustos e profissionais.
O getopts é um comando interno do Bash que processa os argumentos de forma sistemática, permitindo que você defina quais flags são válidas e se elas esperam um argumento adicional. Ele é especialmente útil para scripts que precisam de uma interface de linha de comando clara, seguindo as convenções do Unix. Ao longo desta aula, você verá como declarar flags simples, como lidar com argumentos obrigatórios, e como construir uma mensagem de uso que oriente o usuário.
Flags curtas
Flags curtas são opções de uma única letra precedidas por um hífen, como -a, -b, -c. O getopts permite que você defina quais dessas opções são reconhecidas pelo script. A sintaxe básica é:
getopts ":abc" opcao
Nesse exemplo, a string ":abc" define três opções: -a, -b e -c. O dois-pontos (:) no início é opcional e serve para suprimir mensagens de erro automáticas do getopts, permitindo que você mesmo trate os erros. Cada vez que o getopts é chamado, ele retorna a próxima opção na variável opcao (que você pode nomear como quiser). Você normalmente usa um loop while para processar todas as opções.
Vamos ver um exemplo simples de um script que aceita as flags -a, -b e -c e imprime qual foi fornecida:
#!/bin/bash
while getopts ":abc" opcao; do
case $opcao in
a) echo "Opção -a ativada";;
b) echo "Opção -b ativada";;
c) echo "Opção -c ativada";;
\?) echo "Opção inválida: -$OPTARG" >&2; exit 1;;
esac
done
Ao executar esse script com ./script -a -c, a saída será:
Opção -a ativada
Opção -c ativada
Note que usamos \? para capturar opções inválidas. A variável OPTARG contém o caractere inválido. Esse padrão é fundamental para criar scripts amigáveis.
Argumentos com valor
Muitas opções precisam de um valor associado, como -o arquivo.txt ou -n 10. No getopts, você indica que uma opção espera um argumento adicionando dois-pontos (:) após a letra na string de opções. Por exemplo, a string ":f:n" define que -f e -n exigem um argumento, enquanto -a não exige.
Quando a opção tem um argumento, o valor é armazenado na variável OPTARG. Veja um exemplo prático:
#!/bin/bash
while getopts ":f:n:" opcao; do
case $opcao in
f) echo "Arquivo de saída: $OPTARG";;
n) echo "Número de iterações: $OPTARG";;
\?) echo "Opção inválida: -$OPTARG" >&2; exit 1;;
esac
done
Se você executar ./script -f saida.txt -n 5, a saída será:
Arquivo de saída: saida.txt
Número de iterações: 5
É importante notar que, se uma opção que espera argumento não receber um, o getopts retornará ? ou : dependendo de como você configurou. Para tratar isso, você pode adicionar um caso para : (quando falta o argumento) e \? (opção desconhecida). Vamos melhorar nosso script:
#!/bin/bash
while getopts ":f:n:" opcao; do
case $opcao in
f) echo "Arquivo de saída: $OPTARG";;
n) echo "Número de iterações: $OPTARG";;
:) echo "A opção -$OPTARG requer um argumento." >&2; exit 1;;
\?) echo "Opção inválida: -$OPTARG" >&2; exit 1;;
esac
done
Assim, se o usuário digitar -f sem valor, o script exibirá uma mensagem clara. Esse tratamento de erros é essencial para scripts profissionais.
Mensagem de uso
Uma boa prática em scripts de linha de comando é fornecer uma mensagem de uso (ou help) que explique como usar o script. O getopts não gera automaticamente essa mensagem, mas você pode criar uma função que a exiba quando o usuário usar -h ou quando houver erro. Vamos criar uma função uso() que imprime as instruções:
#!/bin/bash
uso() {
cat <<EOF
Uso: $(basename "$0") [OPÇÕES]
OPÇÕES:
-f ARQUIVO Define o arquivo de saída.
-n NÚMERO Define o número de iterações.
-h Exibe esta mensagem de ajuda.
EOF
}
while getopts ":f:n:h" opcao; do
case $opcao in
f) echo "Arquivo de saída: $OPTARG";;
n) echo "Número de iterações: $OPTARG";;
h) uso; exit 0;;
:) echo "A opção -$OPTARG requer um argumento." >&2; uso; exit 1;;
\?) echo "Opção inválida: -$OPTARG" >&2; uso; exit 1;;
esac
done
Com isso, se o usuário executar ./script -h, verá a mensagem de ajuda. Se ele usar uma opção inválida, também verá a mensagem, além do erro. Isso torna o script muito mais utilizável.
Você pode também exibir a mensagem de uso quando nenhum argumento for passado, verificando se $# é igual a zero. Essa é uma prática comum para evitar que o script execute sem parâmetros.
Exemplos
Vamos montar um exemplo completo que combina tudo o que vimos: um script que processa um arquivo de entrada, um número de repetições e uma flag de verbosidade. O script terá uma função de uso e tratará erros adequadamente.
#!/bin/bash
# Função de uso
uso() {
cat <<EOF
Uso: $(basename "$0") [OPÇÕES]
OPÇÕES:
-i ARQUIVO Arquivo de entrada (obrigatório).
-n NÚMERO Número de repetições (padrão: 1).
-v Modo verboso.
-h Exibe esta mensagem de ajuda.
EOF
}
# Variáveis padrão
arquivo=""
reps=1
verboso=0
# Parsing de opções
while getopts ":i:n:vh" opcao; do
case $opcao in
i) arquivo="$OPTARG";;
n) reps="$OPTARG";;
v) verboso=1;;
h) uso; exit 0;;
:) echo "A opção -$OPTARG requer um argumento." >&2; uso; exit 1;;
\?) echo "Opção inválida: -$OPTARG" >&2; uso; exit 1;;
esac
done
# Verifica se o arquivo foi fornecido
if [[ -z "$arquivo" ]]; then
echo "Erro: o arquivo de entrada é obrigatório." >&2
uso
exit 1
fi
# Verifica se o arquivo existe
if [[ ! -f "$arquivo" ]]; then
echo "Erro: o arquivo '$arquivo' não existe." >&2
exit 1
fi
# Loop de repetições
for ((i=1; i<=reps; i++)); do
if [[ $verboso -eq 1 ]]; then
echo "Processando repetição $i..."
fi
# Simula processamento
cat "$arquivo"
done
echo "Concluído."
Nesse exemplo, usamos -i para o arquivo de entrada, -n para o número de repetições, -v para modo verboso e -h para ajuda. O script valida se o arquivo foi fornecido e se existe. Essa estrutura é típica de scripts mais complexos e mostra como o getopts pode ser integrado a outras lógicas.
Para testar, você pode criar um arquivo dados.txt e executar:
./script.sh -i dados.txt -n 3 -v
Isso exibirá o conteúdo do arquivo três vezes, com mensagens verbosas. Essa abordagem modular facilita a manutenção e a expansão do script.
Boas práticas e observações finais
Ao usar getopts, lembre-se de que ele suporta apenas opções curtas. Para opções longas (como --help), você precisaria usar getopt (com uma letra a mais) ou implementar um parser manual. O getopts também não reordena os argumentos, ou seja, ele processa as opções na ordem em que aparecem, mas ignora os argumentos não-opção (como nomes de arquivos) que são deixados intactos após o loop. Você pode acessá-los via $@ após o shift.
Outra dica é sempre usar o dois-pontos inicial na string de opções para controlar as mensagens de erro, e sempre incluir um caso para : e \?. Isso evita que o script falhe silenciosamente. Além disso, a função de uso deve ser chamada em qualquer erro de sintaxe, para que o usuário saiba como corrigir.
Por fim, teste seu script com várias combinações de argumentos, incluindo casos inválidos, para garantir que o comportamento seja intuitivo. O getopts é uma ferramenta poderosa, mas a qualidade do script depende de como você trata os erros e comunica as opções.
Referências
- Bash Reference Manual - getopts
- POSIX getopts specification
- GNU Bash Manual
- Bash Hackers Wiki - getopts tutorial
- ShellCheck - shell script analysis tool
- Bash Manual - Command Line Arguments
Exercícios
-
Crie um script que use
getoptspara aceitar as opções-a,-be-c(sem argumentos). Para cada opção, imprima uma mensagem indicando que ela foi ativada. Teste com diferentes combinações.✓ Resposta:#!/bin/bash while getopts ":abc" opcao; do case $opcao in a) echo "Opção -a ativada";; b) echo "Opção -b ativada";; c) echo "Opção -c ativada";; \?) echo "Opção inválida: -$OPTARG" >&2; exit 1;; esac done -
Modifique o script anterior para que as opções
-ae-bexijam um argumento (por exemplo,-a valor). Imprima o valor fornecido.✓ Resposta:#!/bin/bash while getopts ":a:b:" opcao; do case $opcao in a) echo "Valor de -a: $OPTARG";; b) echo "Valor de -b: $OPTARG";; :) echo "A opção -$OPTARG requer um argumento." >&2; exit 1;; \?) echo "Opção inválida: -$OPTARG" >&2; exit 1;; esac done -
Escreva um script que aceite uma opção
-o arquivo(obrigatória) e uma opção-v(verboso). Se a opção-onão for fornecida, exiba uma mensagem de erro e a mensagem de uso, e saia com código 1.✓ Resposta:#!/bin/bash uso() { cat <<EOF Uso: $(basename "$0") -o ARQUIVO [-v] -o ARQUIVO Arquivo de saída (obrigatório). -v Modo verboso. EOF } arquivo="" verboso=0 while getopts ":o:v" opcao; do case $opcao in o) arquivo="$OPTARG";; v) verboso=1;; :) echo "A opção -$OPTARG requer um argumento." >&2; uso; exit 1;; \?) echo "Opção inválida: -$OPTARG" >&2; uso; exit 1;; esac done if [[ -z "$arquivo" ]]; then echo "Erro: a opção -o é obrigatória." >&2 uso exit 1 fi if [[ $verboso -eq 1 ]]; then echo "Processando arquivo: $arquivo" fi -
Implemente um script que use
getoptspara aceitar as opções-n(número, com valor) e-h(ajuda). Se-hfor usada, exiba a mensagem de uso e saia. Se-nnão for um número, exiba um erro.✓ Resposta:#!/bin/bash uso() { cat <<EOF Uso: $(basename "$0") -n NÚMERO -n NÚMERO Define o número (obrigatório). -h Exibe esta ajuda. EOF } numero="" while getopts ":n:h" opcao; do case $opcao in n) numero="$OPTARG";; h) uso; exit 0;; :) echo "A opção -$OPTARG requer um argumento." >&2; uso; exit 1;; \?) echo "Opção inválida: -$OPTARG" >&2; uso; exit 1;; esac done if [[ -z "$numero" ]]; then echo "Erro: a opção -n é obrigatória." >&2 uso exit 1 fi if [[ ! "$numero" =~ ^[0-9]+$ ]]; then echo "Erro: '$numero' não é um número válido." >&2 exit 1 fi echo "Número fornecido: $numero" -
Crie um script que aceite múltiplas opções
-f(cada uma com um argumento) e armazene todos os valores em um array. Ao final, imprima todos os valores.✓ Resposta:#!/bin/bash # Array para armazenar os valores valores=() while getopts ":f:" opcao; do case $opcao in f) valores+=("$OPTARG");; :) echo "A opção -$OPTARG requer um argumento." >&2; exit 1;; \?) echo "Opção inválida: -$OPTARG" >&2; exit 1;; esac done echo "Valores fornecidos:" for valor in "${valores[@]}"; do echo "- $valor" done