Cómo obtener argumentos con banderas en Bash

284

Sé que puedo obtener fácilmente parámetros posicionados como este en bash:

$0 o $1

Quiero poder usar opciones de marca como esta para especificar para qué se usa cada parámetro:

mysql -u user -h host

¿Cuál es la mejor manera de obtener -u paramvalor y -h paramvalor por bandera en lugar de por posición?

Stann
fuente
2
Podría ser una buena idea preguntar / check encima en unix.stackexchange.com así
MRR0GERS
8
google para "bash getopts": muchos tutoriales.
Glenn Jackman
89
@ Glenn-Jackman: Definitivamente lo buscaré en Google ahora que sé el nombre. Lo importante de google es que, para hacer una pregunta, ya debe saber el 50% de la respuesta.
Stann

Respuestas:

292

Este es el idioma que suelo usar:

while test $# -gt 0; do
  case "$1" in
    -h|--help)
      echo "$package - attempt to capture frames"
      echo " "
      echo "$package [options] application [arguments]"
      echo " "
      echo "options:"
      echo "-h, --help                show brief help"
      echo "-a, --action=ACTION       specify an action to use"
      echo "-o, --output-dir=DIR      specify a directory to store output in"
      exit 0
      ;;
    -a)
      shift
      if test $# -gt 0; then
        export PROCESS=$1
      else
        echo "no process specified"
        exit 1
      fi
      shift
      ;;
    --action*)
      export PROCESS=`echo $1 | sed -e 's/^[^=]*=//g'`
      shift
      ;;
    -o)
      shift
      if test $# -gt 0; then
        export OUTPUT=$1
      else
        echo "no output dir specified"
        exit 1
      fi
      shift
      ;;
    --output-dir*)
      export OUTPUT=`echo $1 | sed -e 's/^[^=]*=//g'`
      shift
      ;;
    *)
      break
      ;;
  esac
done

Los puntos clave son:

  • $# es la cantidad de argumentos
  • while loop analiza todos los argumentos proporcionados, haciendo coincidir sus valores dentro de una declaración de caso
  • shift quita el primero. Puede cambiar varias veces dentro de una declaración de caso para tomar múltiples valores.
Flexo
fuente
3
¿Qué hacen los casos --action*y --output-dir*?
Lucio
1
Simplemente guardan los valores que obtienen en el medio ambiente.
Flexo
22
@Lucio Super comentario anterior, pero lo agrega en caso de que alguien más visite esta página. El * (comodín) es para el caso donde alguien escribe --action=[ACTION], así como el caso donde alguien escribe--action [ACTION]
cooper
2
¿Por *)qué rompes allí, no deberías salir o ignorar la mala opción? En otras palabras, -bad -o dirla -o dirparte nunca se procesa.
newguy
@newguy buena pregunta. Creo que estaba tratando de dejarlos caer en otra cosa
Flexo
428

Este ejemplo usa el getoptscomando integrado de Bash y es de la Guía de estilo de Google Shell :

a_flag=''
b_flag=''
files=''
verbose='false'

print_usage() {
  printf "Usage: ..."
}

while getopts 'abf:v' flag; do
  case "${flag}" in
    a) a_flag='true' ;;
    b) b_flag='true' ;;
    f) files="${OPTARG}" ;;
    v) verbose='true' ;;
    *) print_usage
       exit 1 ;;
  esac
done

Nota: Si un carácter va seguido de dos puntos (p f:. Ej. ), Se espera que esa opción tenga un argumento.

Ejemplo de uso: ./script -v -a -b -f filename

Usar getopts tiene varias ventajas sobre la respuesta aceptada:

  • la condición while es mucho más legible y muestra cuáles son las opciones aceptadas
  • código más limpio; sin contar el número de parámetros y cambios
  • puedes unir opciones (ej. -a -b -c-abc)

Sin embargo, una gran desventaja es que no admite opciones largas, solo opciones de un solo carácter.

Dennis
fuente
48
Uno se pregunta por qué esta respuesta, usando un bash incorporado, no es la mejor
Will Barnwell
13
Para la posteridad: los dos puntos después de 'abf: v' denotan que -f toma un argumento adicional (el nombre de archivo en este caso).
zahbaz
1
Tuve que cambiar la línea de error a esto:?) printf '\nUsage: %s: [-a] aflag [-b] bflag\n' $0; exit 2 ;;
Andy
77
¿Podría agregar una nota sobre los dos puntos? En eso, después de cada letra, sin dos puntos significa sin arg, un dos puntos significa un argumento y dos puntos significan argumentos opcionales.
limasxgoesto0
3
@WillBarnwell se debe tener en cuenta que se agregó 3 años después de que se hizo la pregunta, mientras que la respuesta principal se agregó el mismo día.
rbennell
47

getopt es tu amigo ... un ejemplo simple:

function f () {
TEMP=`getopt --long -o "u:h:" "$@"`
eval set -- "$TEMP"
while true ; do
    case "$1" in
        -u )
            user=$2
            shift 2
        ;;
        -h )
            host=$2
            shift 2
        ;;
        *)
            break
        ;;
    esac 
done;

echo "user = $user, host = $host"
}

f -u myself -h some_host

Debería haber varios ejemplos en su directorio / usr / bin.

Shizzmo
fuente
3
Un ejemplo más extenso se puede encontrar en el directorio /usr/share/doc/util-linux/examples, al menos en las máquinas Ubuntu.
Serge Stroobandt
10

Creo que esto serviría como un ejemplo más simple de lo que quieres lograr. No hay necesidad de usar herramientas externas. Las herramientas integradas de Bash pueden hacer el trabajo por usted.

function DOSOMETHING {

   while test $# -gt 0; do
           case "$1" in
                -first)
                    shift
                    first_argument=$1
                    shift
                    ;;
                -last)
                    shift
                    last_argument=$1
                    shift
                    ;;
                *)
                   echo "$1 is not a recognized flag!"
                   return 1;
                   ;;
          esac
  done  

  echo "First argument : $first_argument";
  echo "Last argument : $last_argument";
 }

Esto le permitirá usar indicadores para que, sin importar en qué orden pase los parámetros, obtenga el comportamiento adecuado.

Ejemplo:

 DOSOMETHING -last "Adios" -first "Hola"

Salida:

 First argument : Hola
 Last argument : Adios

Puede agregar esta función a su perfil o ponerla dentro de un script.

¡Gracias!

Editar: guarde esto como un archivo y luego ejecútelo como yourfile.sh -last "Adios" -first "Hola"

#!/bin/bash
while test $# -gt 0; do
           case "$1" in
                -first)
                    shift
                    first_argument=$1
                    shift
                    ;;
                -last)
                    shift
                    last_argument=$1
                    shift
                    ;;
                *)
                   echo "$1 is not a recognized flag!"
                   return 1;
                   ;;
          esac
  done  

  echo "First argument : $first_argument";
  echo "Last argument : $last_argument";
Matias Barrios
fuente
Utilizo el código anterior y cuando lo ejecuto no imprime nada. ./hello.sh DOSOMETHING -last "Adios" -first "Hola"
dinu0101
@ dinu0101 Esta es una función. No es un guion. Deberías usarlo como DOSOMETHING -último "Adios" -primero "Hola"
Matias Barrios
Gracias @Matias. Entendido. cómo ejecutar dentro de script.
dinu0101
1
Muchas gracias @Matias
dinu0101
2
Utilizando return 1;con el último ejemplo salidas can only 'return' from a function or sourced scripten macOS Sin exit 1;embargo, el cambio a trabajos funciona como se esperaba.
Mattias
5

Otra alternativa sería usar algo como el siguiente ejemplo que le permitiría usar etiquetas largas --image o short -i y también permitir métodos compilados -i = "example.jpg" o -i example.jpg separados para pasar argumentos .

# declaring a couple of associative arrays
declare -A arguments=();  
declare -A variables=();

# declaring an index integer
declare -i index=1;

# any variables you want to use here
# on the left left side is argument label or key (entered at the command line along with it's value) 
# on the right side is the variable name the value of these arguments should be mapped to.
# (the examples above show how these are being passed into this script)
variables["-gu"]="git_user";  
variables["--git-user"]="git_user";  
variables["-gb"]="git_branch";  
variables["--git-branch"]="git_branch";  
variables["-dbr"]="db_fqdn";  
variables["--db-redirect"]="db_fqdn";  
variables["-e"]="environment";  
variables["--environment"]="environment";

# $@ here represents all arguments passed in
for i in "$@"  
do  
  arguments[$index]=$i;
  prev_index="$(expr $index - 1)";

  # this if block does something akin to "where $i contains ="
  # "%=*" here strips out everything from the = to the end of the argument leaving only the label
  if [[ $i == *"="* ]]
    then argument_label=${i%=*} 
    else argument_label=${arguments[$prev_index]}
  fi

  # this if block only evaluates to true if the argument label exists in the variables array
  if [[ -n ${variables[$argument_label]} ]]
    then
        # dynamically creating variables names using declare
        # "#$argument_label=" here strips out the label leaving only the value
        if [[ $i == *"="* ]]
            then declare ${variables[$argument_label]}=${i#$argument_label=} 
            else declare ${variables[$argument_label]}=${arguments[$index]}
        fi
  fi

  index=index+1;
done;

# then you could simply use the variables like so:
echo "$git_user";
Robert McMahan
fuente
3

Me gusta la respuesta de Robert McMahan la mejor aquí, ya que parece la más fácil de convertir en archivos de inclusión compartibles para que cualquiera de sus scripts los use. Pero parece tener un defecto con la línea que if [[ -n ${variables[$argument_label]} ]]arroja el mensaje "variables: subíndice de matriz incorrecto". Yo no tengo el representante de comentario, y dudo que esto sea el correcto 'arreglar', pero que envolver ifen if [[ -n $argument_label ]] ; thenlo limpia.

Aquí está el código con el que terminé, si conoces una mejor manera, agrega un comentario a la respuesta de Robert.

Incluir archivo "flags-declares.sh"

# declaring a couple of associative arrays
declare -A arguments=();
declare -A variables=();

# declaring an index integer
declare -i index=1;

Incluir archivo "flags-arguments.sh"

# $@ here represents all arguments passed in
for i in "$@"
do
  arguments[$index]=$i;
  prev_index="$(expr $index - 1)";

  # this if block does something akin to "where $i contains ="
  # "%=*" here strips out everything from the = to the end of the argument leaving only the label
  if [[ $i == *"="* ]]
    then argument_label=${i%=*}
    else argument_label=${arguments[$prev_index]}
  fi

  if [[ -n $argument_label ]] ; then
    # this if block only evaluates to true if the argument label exists in the variables array
    if [[ -n ${variables[$argument_label]} ]] ; then
      # dynamically creating variables names using declare
      # "#$argument_label=" here strips out the label leaving only the value
      if [[ $i == *"="* ]]
        then declare ${variables[$argument_label]}=${i#$argument_label=} 
        else declare ${variables[$argument_label]}=${arguments[$index]}
      fi
    fi
  fi

  index=index+1;
done;

Tu "script.sh"

. bin/includes/flags-declares.sh

# any variables you want to use here
# on the left left side is argument label or key (entered at the command line along with it's value) 
# on the right side is the variable name the value of these arguments should be mapped to.
# (the examples above show how these are being passed into this script)
variables["-gu"]="git_user";
variables["--git-user"]="git_user";
variables["-gb"]="git_branch";
variables["--git-branch"]="git_branch";
variables["-dbr"]="db_fqdn";
variables["--db-redirect"]="db_fqdn";
variables["-e"]="environment";
variables["--environment"]="environment";

. bin/includes/flags-arguments.sh

# then you could simply use the variables like so:
echo "$git_user";
echo "$git_branch";
echo "$db_fqdn";
echo "$environment";
Miguel
fuente
3

Si está familiarizado con Python argparse, y no le importa llamar a python para analizar argumentos bash, hay un código que encontré realmente útil y súper fácil de usar llamado argparse-bash https://github.com/nhoffman/ argparse-bash

Ejemplo tomado de su script example.sh:

#!/bin/bash

source $(dirname $0)/argparse.bash || exit 1
argparse "$@" <<EOF || exit 1
parser.add_argument('infile')
parser.add_argument('outfile')
parser.add_argument('-a', '--the-answer', default=42, type=int,
                    help='Pick a number [default %(default)s]')
parser.add_argument('-d', '--do-the-thing', action='store_true',
                    default=False, help='store a boolean [default %(default)s]')
parser.add_argument('-m', '--multiple', nargs='+',
                    help='multiple values allowed')
EOF

echo required infile: "$INFILE"
echo required outfile: "$OUTFILE"
echo the answer: "$THE_ANSWER"
echo -n do the thing?
if [[ $DO_THE_THING ]]; then
    echo " yes, do it"
else
    echo " no, do not do it"
fi
echo -n "arg with multiple values: "
for a in "${MULTIPLE[@]}"; do
    echo -n "[$a] "
done
echo
Linh
fuente
2

Propongo un TLDR simple :; ejemplo para los no iniciados.

Cree un script bash llamado helloworld.sh

#!/bin/bash

while getopts "n:" arg; do
  case $arg in
    n) Name=$OPTARG;;
  esac
done

echo "Hello $Name!"

Luego puede pasar un parámetro opcional -nal ejecutar el script.

Ejecute el script como tal:

$ bash helloworld.sh -n 'World'

Salida

$ Hello World!

Notas

Si desea utilizar múltiples parámetros:

  1. extender while getops "n:" arg: docon más parámetros como while getops "n:o:p:" arg: do
  2. extienda el interruptor de mayúsculas y minúsculas con asignaciones variables adicionales. Tales como o) Option=$OPTARGyp) Parameter=$OPTARG
pijemcolu
fuente
1
#!/bin/bash

if getopts "n:" arg; then
  echo "Welcome $OPTARG"
fi

Guárdelo como sample.sh e intente ejecutar

sh sample.sh -n John

en tu terminal

Nishant Ingle
fuente
1

Tuve problemas para usar getopts con varias banderas, así que escribí este código. Utiliza una variable modal para detectar banderas y para usar esas banderas para asignar argumentos a las variables.

Tenga en cuenta que, si una bandera no debe tener un argumento, se puede hacer algo más que configurar CURRENTFLAG.

    for MYFIELD in "$@"; do

        CHECKFIRST=`echo $MYFIELD | cut -c1`

        if [ "$CHECKFIRST" == "-" ]; then
            mode="flag"
        else
            mode="arg"
        fi

        if [ "$mode" == "flag" ]; then
            case $MYFIELD in
                -a)
                    CURRENTFLAG="VARIABLE_A"
                    ;;
                -b)
                    CURRENTFLAG="VARIABLE_B"
                    ;;
                -c)
                    CURRENTFLAG="VARIABLE_C"
                    ;;
            esac
        elif [ "$mode" == "arg" ]; then
            case $CURRENTFLAG in
                VARIABLE_A)
                    VARIABLE_A="$MYFIELD"
                    ;;
                VARIABLE_B)
                    VARIABLE_B="$MYFIELD"
                    ;;
                VARIABLE_C)
                    VARIABLE_C="$MYFIELD"
                    ;;
            esac
        fi
    done
Jessica Richards
fuente
0

Así que aquí está mi solución. Quería poder manejar banderas booleanas sin guión, con un guión y con dos guiones, así como la asignación de parámetros / valores con uno y dos guiones.

# Handle multiple types of arguments and prints some variables
#
# Boolean flags
# 1) No hyphen
#    create   Assigns `true` to the variable `CREATE`.
#             Default is `CREATE_DEFAULT`.
#    delete   Assigns true to the variable `DELETE`.
#             Default is `DELETE_DEFAULT`.
# 2) One hyphen
#      a      Assigns `true` to a. Default is `false`.
#      b      Assigns `true` to b. Default is `false`.
# 3) Two hyphens
#    cats     Assigns `true` to `cats`. By default is not set.
#    dogs     Assigns `true` to `cats`. By default is not set.
#
# Parameter - Value
# 1) One hyphen
#      c      Assign any value you want
#      d      Assign any value you want
#
# 2) Two hyphens
#   ... Anything really, whatever two-hyphen argument is given that is not
#       defined as flag, will be defined with the next argument after it.
#
# Example:
# ./parser_example.sh delete -a -c VA_1 --cats --dir /path/to/dir
parser() {
    # Define arguments with one hyphen that are boolean flags
    HYPHEN_FLAGS="a b"
    # Define arguments with two hyphens that are boolean flags
    DHYPHEN_FLAGS="cats dogs"

    # Iterate over all the arguments
    while [ $# -gt 0 ]; do
        # Handle the arguments with no hyphen
        if [[ $1 != "-"* ]]; then
            echo "Argument with no hyphen!"
            echo $1
            # Assign true to argument $1
            declare $1=true
            # Shift arguments by one to the left
            shift
        # Handle the arguments with one hyphen
        elif [[ $1 == "-"[A-Za-z0-9]* ]]; then
            # Handle the flags
            if [[ $HYPHEN_FLAGS == *"${1/-/}"* ]]; then
                echo "Argument with one hyphen flag!"
                echo $1
                # Remove the hyphen from $1
                local param="${1/-/}"
                # Assign true to $param
                declare $param=true
                # Shift by one
                shift
            # Handle the parameter-value cases
            else
                echo "Argument with one hyphen value!"
                echo $1 $2
                # Remove the hyphen from $1
                local param="${1/-/}"
                # Assign argument $2 to $param
                declare $param="$2"
                # Shift by two
                shift 2
            fi
        # Handle the arguments with two hyphens
        elif [[ $1 == "--"[A-Za-z0-9]* ]]; then
            # NOTE: For double hyphen I am using `declare -g $param`.
            #   This is the case because I am assuming that's going to be
            #   the final name of the variable
            echo "Argument with two hypens!"
            # Handle the flags
            if [[ $DHYPHEN_FLAGS == *"${1/--/}"* ]]; then
                echo $1 true
                # Remove the hyphens from $1
                local param="${1/--/}"
                # Assign argument $2 to $param
                declare -g $param=true
                # Shift by two
                shift
            # Handle the parameter-value cases
            else
                echo $1 $2
                # Remove the hyphens from $1
                local param="${1/--/}"
                # Assign argument $2 to $param
                declare -g $param="$2"
                # Shift by two
                shift 2
            fi
        fi

    done
    # Default value for arguments with no hypheb
    CREATE=${create:-'CREATE_DEFAULT'}
    DELETE=${delete:-'DELETE_DEFAULT'}
    # Default value for arguments with one hypen flag
    VAR1=${a:-false}
    VAR2=${b:-false}
    # Default value for arguments with value
    # NOTE1: This is just for illustration in one line. We can well create
    #   another function to handle this. Here I am handling the cases where
    #   we have a full named argument and a contraction of it.
    #   For example `--arg1` can be also set with `-c`.
    # NOTE2: What we are doing here is to check if $arg is defined. If not,
    #   check if $c was defined. If not, assign the default value "VD_"
    VAR3=$(if [[ $arg1 ]]; then echo $arg1; else echo ${c:-"VD_1"}; fi)
    VAR4=$(if [[ $arg2 ]]; then echo $arg2; else echo ${d:-"VD_2"}; fi)
}


# Pass all the arguments given to the script to the parser function
parser "$@"


echo $CREATE $DELETE $VAR1 $VAR2 $VAR3 $VAR4 $cats $dir

Algunas referencias

  • El procedimiento principal se encontró aquí .
  • Más sobre pasar todos los argumentos a una función aquí .
  • Más información sobre los valores predeterminados aquí .
  • Más información sobre declaredo $ bash -c "help declare".
  • Más información sobre shiftdo $ bash -c "help shift".
H. Sánchez
fuente