Un ejemplo de cómo usar getopts en bash

Quiero llamar archivo myscript de esta manera:

 $ ./myscript -s 45 -p any_string 

o

 $ ./myscript -h >>> should display help $ ./myscript >>> should display help 

Mis requisitos son:

  • getopt aquí para obtener los argumentos de entrada
  • verificar que -s existe, si no es un error de retorno
  • verifique que el valor después de -s sea ​​45 o 90
  • compruebe que el -p existe y hay una cadena de entrada después
  • si el usuario ingresa ./myscript -h ./myscript simplemente ./myscript entonces muestra ayuda

Intenté hasta ahora este código:

 #!/bin/bash while getopts "h:s:" arg; do case $arg in h) echo "usage" ;; s) strength=$OPTARG echo $strength ;; esac done 

Pero con ese código obtengo errores. ¿Cómo hacerlo con Bash y getopt ?

 #!/bin/bash usage() { echo "Usage: $0 [-s <45|90>] [-p ]" 1>&2; exit 1; } while getopts ":s:p:" o; do case "${o}" in s) s=${OPTARG} ((s == 45 || s == 90)) || usage ;; p) p=${OPTARG} ;; *) usage ;; esac done shift $((OPTIND-1)) if [ -z "${s}" ] || [ -z "${p}" ]; then usage fi echo "s = ${s}" echo "p = ${p}" 

Ejecuciones de ejemplo:

 $ ./myscript.sh Usage: ./myscript.sh [-s <45|90>] [-p ] $ ./myscript.sh -h Usage: ./myscript.sh [-s <45|90>] [-p ] $ ./myscript.sh -s "" -p "" Usage: ./myscript.sh [-s <45|90>] [-p ] $ ./myscript.sh -s 10 -p foo Usage: ./myscript.sh [-s <45|90>] [-p ] $ ./myscript.sh -s 45 -p foo s = 45 p = foo $ ./myscript.sh -s 90 -p bar s = 90 p = bar 

El problema con el código original es que:

  • h: espera el parámetro donde no debería, así que cámbielo en solo h (sin dos puntos)
  • para esperar -p any_string , necesita agregar p: a la lista de argumentos

La syntax básica de getopts es (ver: man bash ):

 getopts OPTSTRING VARNAME [ARGS...] 

dónde:

  • OPTSTRING es una cadena con una lista de argumentos esperados,

    • h – verifique la opción -h sin parámetros; da error en las opciones no compatibles;
    • h: – verifique la opción -h con parámetro; proporciona errores en las opciones no compatibles;
    • abc – compruebe las opciones -a , -b , -c ; proporciona errores en las opciones no compatibles;
    • :abc – compruebe las opciones -a , -b , -c ; silencia los errores en las opciones no compatibles;

      Notas: En otras palabras, dos puntos delante de las opciones le permite manejar los errores en su código. La variable contendrá ? en el caso de la opción no admitida,: en el caso de valor faltante.

  • OPTARG – se establece en el valor del argumento actual,

  • OPTERR – indica si Bash debería mostrar mensajes de error.

Entonces el código puede ser:

 #!/usr/bin/env bash usage() { echo "$0 usage:" && grep " .)\ #" $0; exit 0; } [ $# -eq 0 ] && usage while getopts ":hs:p:" arg; do case $arg in p) # Specify p value. echo "p is ${OPTARG}" ;; s) # Specify strength, either 45 or 90. strength=${OPTARG} [ $strength -eq 45 -o $strength -eq 90 ] \ && echo "Strength is $strength." \ || echo "Strength needs to be either 45 or 90, $strength found instead." ;; h | *) # Display help. usage exit 0 ;; esac done 

Ejemplo de uso:

 $ ./foo.sh ./foo.sh usage: p) # Specify p value. s) # Specify strength, either 45 or 90. h | *) # Display help. $ ./foo.sh -s 123 -p any_string Strength needs to be either 45 or 90, 123 found instead. p is any_string $ ./foo.sh -s 90 -p any_string Strength is 90. p is any_string 

Ver: Pequeño tutorial getopts en Bash Hackers Wiki

El ejemplo empaquetado con getopt (mi distribución lo puso en /usr/share/getopt/getopt-parse.bash ) parece que cubre todos sus casos:

 #!/bin/bash # A small example program for using the new getopt(1) program. # This program will only work with bash(1) # An similar program using the tcsh(1) script language can be found # as parse.tcsh # Example input and output (from the bash prompt): # ./parse.bash -a par1 'another arg' --c-long 'wow!*\?' -cmore -b " very long " # Option a # Option c, no argument # Option c, argument `more' # Option b, argument ` very long ' # Remaining arguments: # --> `par1' # --> `another arg' # --> `wow!*\?' # Note that we use `"$@"' to let each command-line parameter expand to a # separate word. The quotes around `$@' are essential! # We need TEMP as the `eval set --' would nuke the return value of getopt. TEMP=`getopt -o ab:c:: --long a-long,b-long:,c-long:: \ -n 'example.bash' -- "$@"` if [ $? != 0 ] ; then echo "Terminating..." >&2 ; exit 1 ; fi # Note the quotes around `$TEMP': they are essential! eval set -- "$TEMP" while true ; do case "$1" in -a|--a-long) echo "Option a" ; shift ;; -b|--b-long) echo "Option b, argument \`$2'" ; shift 2 ;; -c|--c-long) # c has an optional argument. As we are in quoted mode, # an empty parameter will be generated if its optional # argument is not found. case "$2" in "") echo "Option c, no argument"; shift 2 ;; *) echo "Option c, argument \`$2'" ; shift 2 ;; esac ;; --) shift ; break ;; *) echo "Internal error!" ; exit 1 ;; esac done echo "Remaining arguments:" for arg do echo '--> '"\`$arg'" ; done 

Sé que esto ya está respondido, pero para el registro y para cualquier persona con los mismos requisitos que yo, decidí publicar esta respuesta relacionada. El código está inundado de comentarios para explicar el código.

Respuesta actualizada:

Guarde el archivo como getopt.sh :

 #!/bin/bash function get_variable_name_for_option { local OPT_DESC=${1} local OPTION=${2} local VAR=$(echo ${OPT_DESC} | sed -e "s/.*\[\?-${OPTION} \([A-Z_]\+\).*/\1/g" -e "s/.*\[\?-\(${OPTION}\).*/\1FLAG/g") if [[ "${VAR}" == "${1}" ]]; then echo "" else echo ${VAR} fi } function parse_options { local OPT_DESC=${1} local INPUT=$(get_input_for_getopts "${OPT_DESC}") shift while getopts ${INPUT} OPTION ${@}; do [ ${OPTION} == "?" ] && usage VARNAME=$(get_variable_name_for_option "${OPT_DESC}" "${OPTION}") [ "${VARNAME}" != "" ] && eval "${VARNAME}=${OPTARG:-true}" # && printf "\t%s\n" "* Declaring ${VARNAME}=${!VARNAME} -- OPTIONS='$OPTION'" done check_for_required "${OPT_DESC}" } function check_for_required { local OPT_DESC=${1} local REQUIRED=$(get_required "${OPT_DESC}" | sed -e "s/\://g") while test -n "${REQUIRED}"; do OPTION=${REQUIRED:0:1} VARNAME=$(get_variable_name_for_option "${OPT_DESC}" "${OPTION}") [ -z "${!VARNAME}" ] && printf "ERROR: %s\n" "Option -${OPTION} must been set." && usage REQUIRED=${REQUIRED:1} done } function get_input_for_getopts { local OPT_DESC=${1} echo ${OPT_DESC} | sed -e "s/\([a-zA-Z]\) [A-Z_]\+/\1:/g" -e "s/[][ -]//g" } function get_optional { local OPT_DESC=${1} echo ${OPT_DESC} | sed -e "s/[^[]*\(\[[^]]*\]\)[^[]*/\1/g" -e "s/\([a-zA-Z]\) [A-Z_]\+/\1:/g" -e "s/[][ -]//g" } function get_required { local OPT_DESC=${1} echo ${OPT_DESC} | sed -e "s/\([a-zA-Z]\) [A-Z_]\+/\1:/g" -e "s/\[[^[]*\]//g" -e "s/[][ -]//g" } function usage { printf "Usage:\n\t%s\n" "${0} ${OPT_DESC}" exit 10 } 

Entonces puedes usarlo así:

 #!/bin/bash # # [ and ] defines optional arguments # # location to getopts.sh file source ./getopt.sh USAGE="-u USER -d DATABASE -p PASS -s SID [ -a START_DATE_TIME ]" parse_options "${USAGE}" ${@} echo ${USER} echo ${START_DATE_TIME} 

Respuesta anterior:

Recientemente necesité usar un enfoque genérico. Me encontré con esta solución:

 #!/bin/bash # Option Description: # ------------------- # # Option description is based on getopts bash builtin. The description adds a variable name feature to be used # on future checks for required or optional values. # The option description adds "=>VARIABLE_NAME" string. Variable name should be UPPERCASE. Valid characters # are [A-Z_]*. # # A option description example: # OPT_DESC="a:=>A_VARIABLE|b:=>B_VARIABLE|c=>C_VARIABLE" # # -a option will require a value (the colon means that) and should be saved in variable A_VARIABLE. # "|" is used to separate options description. # -b option rule applies the same as -a. # -c option doesn't require a value (the colon absense means that) and its existence should be set in C_VARIABLE # # ~$ echo get_options ${OPT_DESC} # a:b:c # ~$ # # Required options REQUIRED_DESC="a:=>REQ_A_VAR_VALUE|B:=>REQ_B_VAR_VALUE|c=>REQ_C_VAR_FLAG" # Optional options (duh) OPTIONAL_DESC="P:=>OPT_P_VAR_VALUE|r=>OPT_R_VAR_FLAG" function usage { IFS="|" printf "%s" ${0} for i in ${REQUIRED_DESC}; do VARNAME=$(echo $i | sed -e "s/.*=>//g") printf " %s" "-${i:0:1} $VARNAME" done for i in ${OPTIONAL_DESC}; do VARNAME=$(echo $i | sed -e "s/.*=>//g") printf " %s" "[-${i:0:1} $VARNAME]" done printf "\n" unset IFS exit } # Auxiliary function that returns options characters to be passed # into 'getopts' from a option description. # Arguments: # $1: The options description (SEE TOP) # # Example: # OPT_DESC="h:=>H_VAR|f:=>F_VAR|P=>P_VAR|W=>W_VAR" # OPTIONS=$(get_options ${OPT_DESC}) # echo "${OPTIONS}" # # Output: # "h:f:PW" function get_options { echo ${1} | sed -e "s/\([a-zA-Z]\:\?\)=>[A-Z_]*|\?/\1/g" } # Auxiliary function that returns all variable names separated by '|' # Arguments: # $1: The options description (SEE TOP) # # Example: # OPT_DESC="h:=>H_VAR|f:=>F_VAR|P=>P_VAR|W=>W_VAR" # VARNAMES=$(get_values ${OPT_DESC}) # echo "${VARNAMES}" # # Output: # "H_VAR|F_VAR|P_VAR|W_VAR" function get_variables { echo ${1} | sed -e "s/[a-zA-Z]\:\?=>\([^|]*\)/\1/g" } # Auxiliary function that returns the variable name based on the # option passed by. # Arguments: # $1: The options description (SEE TOP) # $2: The option which the variable name wants to be retrieved # # Example: # OPT_DESC="h:=>H_VAR|f:=>F_VAR|P=>P_VAR|W=>W_VAR" # H_VAR=$(get_variable_name ${OPT_DESC} "h") # echo "${H_VAR}" # # Output: # "H_VAR" function get_variable_name { VAR=$(echo ${1} | sed -e "s/.*${2}\:\?=>\([^|]*\).*/\1/g") if [[ ${VAR} == ${1} ]]; then echo "" else echo ${VAR} fi } # Gets the required options from the required description REQUIRED=$(get_options ${REQUIRED_DESC}) # Gets the optional options (duh) from the optional description OPTIONAL=$(get_options ${OPTIONAL_DESC}) # or... $(get_options "${OPTIONAL_DESC}|${REQUIRED_DESC}") # The colon at starts instructs getopts to remain silent while getopts ":${REQUIRED}${OPTIONAL}" OPTION do [[ ${OPTION} == ":" ]] && usage VAR=$(get_variable_name "${REQUIRED_DESC}|${OPTIONAL_DESC}" ${OPTION}) [[ -n ${VAR} ]] && eval "$VAR=${OPTARG}" done shift $(($OPTIND - 1)) # Checks for required options. Report an error and exits if # required options are missing. # Using function version ... VARS=$(get_variables ${REQUIRED_DESC}) IFS="|" for VARNAME in $VARS; do [[ -v ${VARNAME} ]] || usage done unset IFS # ... or using IFS Version (no function) OLDIFS=${IFS} IFS="|" for i in ${REQUIRED_DESC}; do VARNAME=$(echo $i | sed -e "s/.*=>//g") [[ -v ${VARNAME} ]] || usage printf "%s %s %s\n" "-${i:0:1}" "${!VARNAME:=present}" "${VARNAME}" done IFS=${OLDIFS} 

No probé esto más o menos, así que podría tener algunos errores allí.

Ejemplo de POSIX 7

También vale la pena consultar el ejemplo del estándar: http://pubs.opengroup.org/onlinepubs/9699919799/utilities/getopts.html

 aflag= bflag= while getopts ab: name do case $name in a) aflag=1;; b) bflag=1 bval="$OPTARG";; ?) printf "Usage: %s: [-a] [-b value] args\n" $0 exit 2;; esac done if [ ! -z "$aflag" ]; then printf "Option -a specified\n" fi if [ ! -z "$bflag" ]; then printf 'Option -b "%s" specified\n' "$bval" fi shift $(($OPTIND - 1)) printf "Remaining arguments are: %s\n" "$*" 

Y luego podemos probarlo:

 $ sh a.sh Remaining arguments are: $ sh a.sh -a Option -a specified Remaining arguments are: $ sh a.sh -b No arg for -b option Usage: a.sh: [-a] [-b value] args $ sh a.sh -b myval Option -b "myval" specified Remaining arguments are: $ sh a.sh -a -b myval Option -a specified Option -b "myval" specified Remaining arguments are: $ sh a.sh remain Remaining arguments are: remain $ sh a.sh -- -a remain Remaining arguments are: -a remain 

Probado en Ubuntu 17.10, sh es dash 0.5.8.