Estou tentando criar um script de shell que aceita várias opções e getopts
parece ser uma boa solução, pois pode lidar com a ordem variável das opções e argumentos (eu acho!).
Usarei apenas opções curtas e cada opção curta exigirá um valor correspondente, por exemplo: ./command.sh -a arga -g argg -b argb
mas gostaria de permitir que as opções fossem inseridas em uma ordem não específica, como é o modo como a maioria das pessoas está acostumada a trabalhar com comandos do shell .
O outro ponto é que eu gostaria de fazer minha própria verificação dos valores dos argumentos das opções, idealmente dentro das case
instruções. A razão para isso é que meus testes :)
em minha case
declaração produziram resultados inconsistentes (provavelmente por falta de entendimento da minha parte).
Por exemplo:
#!/bin/bash
OPTIND=1 # Reset if getopts used previously
if (($# == 0)); then
echo "Usage"
exit 2
fi
while getopts ":h:u:p:d:" opt; do
case "$opt" in
h)
MYSQL_HOST=$OPTARG
;;
u)
MYSQL_USER=$OPTARG
;;
p)
MYSQL_PASS=$OPTARG
;;
d)
BACKUP_DIR=$OPTARG
;;
\?)
echo "Invalid option: -$OPTARG" >&2
exit 2;;
:)
echo "Option -$OPTARG requires an argument" >&2
exit 2;;
esac
done
shift $((OPTIND-1))
echo "MYSQL_HOST='$MYSQL_HOST' MYSQL_USER='$MYSQL_USER' MYSQL_PASS='$MYSQL_PASS' BACKUP_DIR='$BACKUP_DIR' Additionals: $@"
Estava falhando em ocorrências como esta ... ./command.sh -d -h
Quando eu quero sinalizar -d como exigindo um argumento, mas recebo o valor de -d=-h
que não é o que eu preciso.
Portanto, imaginei que seria mais fácil executar minha própria validação nas instruções de caso para garantir que cada opção seja definida e definida apenas uma vez.
Estou tentando fazer o seguinte, mas meus if [ ! "$MYSQL_HOST" ]; then
bloqueios não são acionados.
OPTIND=1 # Reset if getopts used previously
if (($# == 0)); then
echo "Usage"
exit 2
fi
while getopts ":h:u:p:d:" opt; do
case "$opt" in
h)
MYSQL_HOST=$OPTARG
if [ ! "$MYSQL_HOST" ]; then
echo "host not set"
exit 2
fi
;;
u)
MYSQL_USER=$OPTARG
if [ ! "$MYSQL_USER" ]; then
echo "username not set"
exit 2
fi
;;
p)
MYSQL_PASS=$OPTARG
if [ ! "$MYSQL_PASS" ]; then
echo "password not set"
exit 2
fi
;;
d)
BACKUP_DIR=$OPTARG
if [ ! "$BACKUP_DIR" ]; then
echo "backup dir not set"
exit 2
fi
;;
\?)
echo "Invalid option: -$OPTARG" >&2
exit 2;;
#:)
# echo "Option -$opt requires an argument" >&2
# exit 2;;
esac
done
shift $((OPTIND-1))
echo "MYSQL_HOST='$MYSQL_HOST' MYSQL_USER='$MYSQL_USER' MYSQL_PASS='$MYSQL_PASS' BACKUP_DIR='$BACKUP_DIR' Additionals: $@"
Existe uma razão pela qual não consigo verificar se uma OPTARG
tem comprimento zero a partir de dentro getopts ... while ... case
?
Qual é a melhor maneira de executar minha própria validação de argumento getopts
em um caso em que não quero depender da :)
. Executar minha validação de argumento fora do while ... case ... esac
?
Então eu poderia acabar com valores de argumento de -d
etc e não pegar uma opção que faltava.
while
loop sobre as opções. Esse é o método que eu uso, embora seja detalhado em comparação com outros, é muito claro o que está acontecendo no script. E a manutenção por outros é importante neste caso.Como as outras respostas não respondem muito à sua pergunta: este é um comportamento esperado e com base em como o POSIX está sendo interpretado:
Isso é tudo o que está sendo afirmado, e para resumir uma história (não muito longa):
getopts
não sabe magicamente que o argumento perfeitamente válido que ele vê para a opção que você precisa para ter um argumento na verdade não é um argumento de opção, mas outra opção . Nada impede que você tenha-h
um argumento válido para a-d
opção, o que, na prática, significa quegetopts
apenas gerará um erro se sua opção que requer um argumento vier por último na linha de comando, por exemplo:test.sh
:Exemplo:
A razão pela qual sua segunda abordagem não funciona é porque a análise
getopts
ainda é a mesma; portanto, uma vez dentro do loop, o "dano" já está feito.Se você absolutamente deseja proibir isso, então, como Benubird apontou, você terá que verificar seus argumentos de opção e gerar um erro se eles equivalerem a uma opção válida.
fonte
Eu continuaria a usar,
getopts
mas faria uma verificação extra depois de: (não testado)requer bash versão 4
fonte
O getopt interno não shell do Gnu funciona menos para você, mas permite maior flexibilidade para determinar o que acontece depois que uma flag é encontrada, incluindo a desativação dos argumentos.
Encontrei um artigo comparando getopts e getopt, o que provavelmente será útil, pois o manual é meio difícil de entender (pelo menos antes do café).
fonte
Primeiro, você deve estar usando
if [ -z "$MYSQL_USER" ]
.Em segundo lugar, não há necessidade da tarefa -
if [ -z "$OPTARG" ]
funcionará bem.Em terceiro lugar, eu suspeito que você realmente quer
if [ ${#OPTARG} = 0 ]
. $ {# x} é uma coisa do bash que retorna o comprimento da string $ x (veja mais aqui ).Em quarto lugar, se você estiver fazendo sua própria validação, recomendo o getopt em vez de getopts, pois ele oferece muito mais flexibilidade.
Por fim, para responder sua primeira pergunta, você pode detectar quando uma bandeira é passada como argumento colocando uma lista de bandeiras no topo, da seguinte maneira:
E, em seguida, marque a opção em que deseja verificar se o argumento fornecido é uma opção, algo como isto:
Não é uma resposta perfeita, mas funciona! Seu problema é que "-h" é um argumento perfeitamente válido, e você não pode dar ao shell saber que está apenas procurando parâmetros que também não são sinalizadores válidos.
fonte