Estoy tratando de construir un script de shell que acepte varias opciones y getopts
parezca una buena solución, ya que puede manejar el orden variable de las opciones y argumentos (¡creo!).
Solo usaré opciones cortas y cada opción corta requerirá un valor correspondiente, por ejemplo: ./command.sh -a arga -g argg -b argb
pero me gustaría permitir que las opciones se ingresen en un orden no específico, como es la forma en que la mayoría de la gente está acostumbrada a trabajar con comandos de shell .
El otro punto es que me gustaría hacer mi propia verificación de los valores de argumento de opción, idealmente dentro de las case
declaraciones. La razón de esto es que mi prueba de :)
en mi case
declaración ha arrojado resultados inconsistentes (probablemente por falta de comprensión de mi parte).
Por ejemplo:
#!/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: $@"
Estaba fallando en casos como este ... ./command.sh -d -h
Cuando quiero que marque -d como que requiere un argumento, pero obtengo el valor de lo -d=-h
cual no es lo que necesito.
Así que pensé que sería más fácil ejecutar mi propia validación dentro de las declaraciones de caso para garantizar que cada opción se establezca y se configure solo una vez.
Estoy tratando de hacer lo siguiente pero mis if [ ! "$MYSQL_HOST" ]; then
bloques no se activan.
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: $@"
¿Hay alguna razón por la que no puedo verificar si una OPTARG
tiene longitud cero desde dentro getopts ... while ... case
?
¿Cuál es la mejor manera de ejecutar mi propia validación de argumentos getopts
en un caso en el que no quiero confiar en el :)
. Realizar la validación de mi argumento fuera de while ... case ... esac
?
Entonces podría terminar con valores de argumento de -d
etc. y no detectar una opción faltante.
while
bucle sobre las opciones. Este es el método que utilicé, ya que aunque es detallado en comparación con otros, está muy claro lo que está sucediendo en el script. Y la mantenibilidad por otros es importante en este caso.Dado que las otras respuestas en realidad no responden tanto a su pregunta: este es el comportamiento esperado y se basa en cómo se interpreta POSIX :
Esto es todo lo que se está diciendo, y para resumir (no tanto) la historia:
getopts
no sabe mágicamente que el argumento perfectamente válido que ve para la opción que necesita para tener un argumento no es en realidad un argumento de opción, sino otra opción . Nada le impide tener-h
un argumento válido para la-d
opción, lo que en la práctica significa quegetopts
solo arrojará un error si su opción que requiere un argumento ocupa el último lugar en la línea de comando, por ejemplo:test.sh
:Ejemplo:
La razón por la cual su segundo enfoque no funciona es porque el análisis que
getopts
sigue siendo el mismo, por lo que una vez que está dentro del bucle, el "daño" ya está hecho.Si absolutamente quiere prohibir esto, entonces, como señaló Benubird, tendrá que verificar sus argumentos de opción usted mismo y lanzar un error si equivalen a una opción válida.
fuente
Continuaría usando,
getopts
pero haría algunas comprobaciones adicionales después de: (no probado)requiere bash versión 4
fuente
El getopt incorporado no shell de Gnu funciona menos para usted, pero permite una mayor flexibilidad a medida que determina qué sucede después de que se encuentra una bandera, incluido el desplazamiento de los argumentos usted mismo.
Encontré un artículo que compara getopts y getopt que probablemente será útil, ya que es difícil entender el manual (al menos antes del café).
fuente
Primero, deberías estar usando
if [ -z "$MYSQL_USER" ]
.En segundo lugar, no hay necesidad de la asignación
if [ -z "$OPTARG" ]
, funcionará bien.En tercer lugar, sospecho que lo que realmente quieres es
if [ ${#OPTARG} = 0 ]
. $ {# x} es una cosa bash que devuelve la longitud de la cadena $ x (vea más aquí ).En cuarto lugar, si está haciendo su propia validación, recomendaría getopt en lugar de getopts, ya que proporciona mucha más flexibilidad.
Finalmente, para responder a su primera pregunta, puede detectar cuándo se pasa una bandera como argumento colocando una lista de banderas en la parte superior, como esta:
Y luego verificando la opción en la que desea verificar si el argumento proporcionado es una opción, algo como esto:
No es una respuesta perfecta, ¡pero funciona! Su problema es que "-h" es un argumento perfectamente válido, y no le da forma al shell de saber que solo está buscando parámetros que no sean también indicadores válidos.
fuente