Come ottenere argomenti con flag in Bash


284

So che posso facilmente ottenere parametri posizionati come questo in bash:

$0 o $1

Voglio essere in grado di utilizzare opzioni di flag come questa per specificare per quale parametro viene utilizzato:

mysql -u user -h host

Qual è il modo migliore per ottenere -u paramvalore e -h paramvalore per flag anziché per posizione?


2
Potrebbe essere una buona idea chiedere / check sopra a unix.stackexchange.com così
MRR0GERS

8
google per "bash getopts" - molti tutorial.
Glenn Jackman,

89
@ glenn-jackman: lo cercherò sicuramente ora che conosco il nome. La cosa su google è - per porre una domanda - dovresti già conoscere il 50% della risposta.
Stann,

Risposte:


292

Questo è il linguaggio che di solito uso:

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

I punti chiave sono:

  • $# è il numero di argomenti
  • while loop esamina tutti gli argomenti forniti, facendo corrispondere i loro valori all'interno di un'istruzione case
  • il turno porta via il primo. È possibile spostare più volte all'interno di un'istruzione case per assumere più valori.

3
Cosa fanno i casi --action*e --output-dir*?
Lucio,

1
Salvano semplicemente i valori che ottengono nell'ambiente.
Flexo

22
@Lucio Super vecchio commento, ma aggiungendolo nel caso in cui qualcun altro abbia mai visitato questa pagina. Il * (carattere jolly) è per il caso in cui qualcuno digita --action=[ACTION], nonché per il caso in cui qualcuno digita--action [ACTION]
cooper

2
Perché *)ti rompi lì, non dovresti uscire o ignorare l'opzione sbagliata? In altre parole, -bad -o dirla -o dirparte non viene mai elaborata.
Newguy

@newguy buona domanda. Penso che stavo cercando di farli cadere in qualcos'altro
Flexo

428

Questo esempio utilizza il getoptscomando integrato di Bash ed è tratto dalla Guida allo stile della shell di Google :

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: se un carattere è seguito da due punti (ad es. f:), Si prevede che quell'opzione abbia un argomento.

Esempio di utilizzo: ./script -v -a -b -f filename

L'uso di getopts presenta diversi vantaggi rispetto alla risposta accettata:

  • la condizione while è molto più leggibile e mostra quali sono le opzioni accettate
  • codice più pulito; senza contare il numero di parametri e lo spostamento
  • puoi unirti alle opzioni (es. -a -b -c-abc)

Tuttavia, un grosso svantaggio è che non supporta opzioni lunghe, solo opzioni a carattere singolo.


48
Ci si chiede perché questa risposta, usando un built-in bash, non sia la migliore
Will Barnwell,

13
Per i posteri: i due punti dopo in 'abf: v' indicano che -f accetta un argomento aggiuntivo (il nome del file in questo caso).
Zahbaz,

1
Ho dovuto cambiare la linea di errore in questo:?) printf '\nUsage: %s: [-a] aflag [-b] bflag\n' $0; exit 2 ;;
Andy,

7
Potresti aggiungere una nota sui due punti? In quanto dopo ogni lettera, nessun punto indica nessun argomento, un punto indica un argomento e due punti indicano un argomento opzionale?
limasxgoesto0,

3
@WillBarnwell si dovrebbe notare che è stato aggiunto 3 anni dopo la domanda, mentre la risposta principale è stata aggiunta lo stesso giorno.
rbennell,

47

getopt è tuo amico .. un semplice esempio:

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

Dovrebbero esserci vari esempi nella tua directory / usr / bin.


3
Un esempio più ampio può essere trovato nella directory /usr/share/doc/util-linux/examples, almeno sulle macchine Ubuntu.
Serge Stroobandt,

10

Penso che questo servirebbe da esempio più semplice di ciò che vuoi ottenere. Non è necessario utilizzare strumenti esterni. Gli strumenti integrati di Bash possono fare al caso tuo.

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";
 }

Ciò ti consentirà di utilizzare i flag, quindi, indipendentemente dall'ordine in cui stai passando i parametri, otterrai il comportamento corretto.

Esempio :

 DOSOMETHING -last "Adios" -first "Hola"

Produzione :

 First argument : Hola
 Last argument : Adios

Puoi aggiungere questa funzione al tuo profilo o inserirla in uno script.

Grazie!

Modifica: salva questo come file e quindi eseguilo come 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";

Uso il codice sopra e quando eseguito non stampa nulla. ./hello.sh DOSOMETHING -last "Adios" -first "Hola"
dinu0101,

@ dinu0101 Questa è una funzione. Non una sceneggiatura. Dovresti usarlo come DOSOMETHING -last "Adios" -prima "Hola"
Matias Barrios il

Grazie @Matias. Inteso. come eseguire all'interno dello script.
dinu0101,

1
Grazie mille @Matias
dinu0101

2
Utilizzo return 1;con l'ultimo output di output can only 'return' from a function or sourced scriptsu macOS. Il passaggio a exit 1;funziona come previsto però.
Mattias,

5

Un'altra alternativa sarebbe quella di usare qualcosa come l'esempio seguente che ti consenta di usare tag lunghi --image o -i brevi e consentire anche metodi compilati -i = "esempio.jpg" o separati -i esempio.jpg per passare argomenti .

# 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";

3

Mi piace la risposta di Robert McMahan il migliore qui in quanto sembra il più facile da trasformare in file includibili condivisibili per qualsiasi tuo script da utilizzare. Ma sembra avere un difetto con la riga che if [[ -n ${variables[$argument_label]} ]]lancia il messaggio "variabili: indice di array non valido". Non ho il rappresentante per commentare, e dubito che questa sia la corretta "correzione", ma la sua conclusione ifnel if [[ -n $argument_label ]] ; thenripulire.

Ecco il codice con cui sono finito, se conosci un modo migliore per favore aggiungi un commento alla risposta di Robert.

Includi il file "flags-declares.sh"

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

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

Includi il file "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;

Il tuo "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";

3

Se hai familiarità con Python argparse e non ti dispiace chiamare python per analizzare gli argomenti di bash, c'è un pezzo di codice che ho trovato davvero utile e super facile da usare chiamato argparse-bash https://github.com/nhoffman/ argparse-bash

L'esempio prende dal loro 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

2

Propongo un semplice TLDR :; esempio per i non iniziati.

Crea uno script bash chiamato helloworld.sh

#!/bin/bash

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

echo "Hello $Name!"

È quindi possibile passare un parametro facoltativo -ndurante l'esecuzione dello script.

Eseguire lo script come tale:

$ bash helloworld.sh -n 'World'

Produzione

$ Hello World!

Appunti

Se desideri utilizzare più parametri:

  1. estendere while getops "n:" arg: docon più parametri come while getops "n:o:p:" arg: do
  2. estendere l'interruttore del case con assegnazioni di variabili extra. Come o) Option=$OPTARGep) Parameter=$OPTARG

1
#!/bin/bash

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

Salvalo come sample.sh e prova a eseguirlo

sh sample.sh -n John

nel tuo terminale.


1

Ho avuto problemi con l'utilizzo di getopts con più flag, quindi ho scritto questo codice. Utilizza una variabile modale per rilevare flag e per utilizzare tali flag per assegnare argomenti alle variabili.

Nota che, se una bandiera non dovrebbe avere un argomento, si può fare qualcosa di diverso dall'impostazione di 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

0

Quindi ecco la mia soluzione. Volevo essere in grado di gestire flag booleani senza trattino, con un trattino e con due trattini e l'assegnazione di parametri / valori con uno e due trattini.

# 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

Alcuni riferimenti

  • La procedura principale è stata trovata qui .
  • Maggiori informazioni sul passaggio di tutti gli argomenti a una funzione qui .
  • Maggiori informazioni sui valori predefiniti qui .
  • Maggiori informazioni su declaredo $ bash -c "help declare".
  • Maggiori informazioni su shiftdo $ bash -c "help shift".
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.