So erhalten Sie Argumente mit Flags in Bash

283

Ich weiß, dass ich in bash leicht positionierte Parameter wie diesen erhalten kann:

$0 oder $1

Ich möchte in der Lage sein, Flag-Optionen wie diese zu verwenden, um anzugeben, wofür jeder Parameter verwendet wird:

mysql -u user -h host

Was ist der beste Weg, um -u paramWert und -h paramWert nach Flag anstatt nach Position zu erhalten?

Stann
quelle
2
Es könnte eine gute Idee zu fragen / Check über sein unix.stackexchange.com auch
MRR0GERS
8
google for "bash getopts" - viele Tutorials.
Glenn Jackman
89
@ Glenn-Jackman: Ich werde es jetzt definitiv googeln, da ich den Namen kenne. Die Sache mit Google ist - um eine Frage zu stellen - Sie sollten bereits 50% der Antwort kennen.
Stann

Antworten:

291

Dies ist die Redewendung, die ich normalerweise verwende:

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

Wichtige Punkte sind:

  • $# ist die Anzahl der Argumente
  • while-Schleife betrachtet alle angegebenen Argumente und stimmt mit ihren Werten in einer case-Anweisung überein
  • Schicht nimmt den ersten weg. Sie können innerhalb einer case-Anweisung mehrere Male verschieben, um mehrere Werte anzunehmen.
Flexo
quelle
3
Was machen die --action*und --output-dir*Fälle?
Lucio
1
Sie speichern nur die Werte, die sie in die Umgebung gelangen.
Flexo
22
@ Lucio Super alter Kommentar, aber für den Fall, dass jemand anderes diese Seite jemals besucht. Das * (Platzhalter) ist für den Fall, dass jemand tippt --action=[ACTION], sowie für den Fall, dass jemand tippt--action [ACTION]
Cooper
2
Warum *)brechen Sie dort ab, sollten Sie die schlechte Option nicht verlassen oder ignorieren? Mit anderen Worten, -bad -o dirdas -o dirTeil wird niemals verarbeitet.
Newguy
@ Newguy gute Frage. Ich glaube, ich habe versucht, sie zu etwas anderem
durchfallen
427

In diesem Beispiel wird der in Bash integrierte getoptsBefehl verwendet und stammt aus dem Google Shell Style Guide :

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

Hinweis: Wenn auf ein Zeichen ein Doppelpunkt folgt (z. B. f:), wird erwartet, dass diese Option ein Argument enthält.

Anwendungsbeispiel: ./script -v -a -b -f filename

Die Verwendung von getopts hat gegenüber der akzeptierten Antwort mehrere Vorteile:

  • Die while-Bedingung ist viel besser lesbar und zeigt, welche Optionen akzeptiert werden
  • sauberer Code; kein Zählen der Anzahl der Parameter und Verschieben
  • Sie können Optionen beitreten (zB -a -b -c-abc)

Ein großer Nachteil ist jedoch, dass keine langen Optionen unterstützt werden, sondern nur Optionen mit nur einem Zeichen.

Dennis
quelle
48
Man wundert sich, warum diese Antwort mit einem eingebauten Bash nicht die beste ist
Will Barnwell
13
Für die Nachwelt: Der Doppelpunkt nach 'abf: v' bedeutet, dass -f ein zusätzliches Argument (in diesem Fall den Dateinamen) verwendet.
Zahbaz
1
Ich musste die Fehlerzeile folgendermaßen ändern:?) printf '\nUsage: %s: [-a] aflag [-b] bflag\n' $0; exit 2 ;;
Andy
7
Könnten Sie einen Hinweis zu den Doppelpunkten hinzufügen? Nach jedem Buchstaben bedeutet kein Doppelpunkt kein Argument, ein Doppelpunkt bedeutet ein Argument und zwei Doppelpunkte bedeuten optionales Argument?
limasxgoesto0
3
@ WillBarnwell sollte man beachten, dass es 3 Jahre nach der Frage hinzugefügt wurde, während die Top-Antwort am selben Tag hinzugefügt wurde.
Rbennell
47

getopt ist dein Freund .. ein einfaches Beispiel:

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

In Ihrem Verzeichnis / usr / bin sollten sich verschiedene Beispiele befinden.

Shizzmo
quelle
3
Ein ausführlicheres Beispiel finden Sie im Verzeichnis /usr/share/doc/util-linux/examples, zumindest auf Ubuntu-Computern.
Serge Stroobandt
10

Ich denke, dies wäre ein einfacheres Beispiel dafür, was Sie erreichen möchten. Es ist nicht erforderlich, externe Tools zu verwenden. Eingebaute Bash-Tools können die Arbeit für Sie erledigen.

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

Auf diese Weise können Sie Flags verwenden, sodass Sie unabhängig von der Reihenfolge, in der Sie die Parameter übergeben, das richtige Verhalten erhalten.

Beispiel:

 DOSOMETHING -last "Adios" -first "Hola"

Ausgabe :

 First argument : Hola
 Last argument : Adios

Sie können diese Funktion Ihrem Profil hinzufügen oder in ein Skript einfügen.

Vielen Dank!

Bearbeiten: Speichern Sie diese als aa-Datei und führen Sie sie dann als aus 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
quelle
Ich verwende den obigen Code und wenn er ausgeführt wird, wird nichts gedruckt. ./hello.sh DOSOMETHING -last "Adios" -first "Hola"
dinu0101
@ dinu0101 Dies ist eine Funktion. Kein Skript. Sie sollten es als DOSOMETHING -last "Adios" -first "Hola"
Matias Barrios
Danke @Matias. Verstanden. wie man innerhalb des Skripts läuft.
dinu0101
1
Vielen Dank @Matias
dinu0101
2
Verwenden Sie return 1;mit dem letzten Beispiel Ausgaben can only 'return' from a function or sourced scriptunter macOS. Der Wechsel zu exit 1;funktioniert jedoch wie erwartet.
Mattias
5

Eine andere Alternative wäre, etwas wie das folgende Beispiel zu verwenden, mit dem Sie lange --image- oder kurze -i- Tags verwenden und auch kompilierte -i = "example.jpg" - oder separate -i example.jpg- Methoden zum Übergeben von Argumenten zulassen können .

# 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
quelle
3

Ich mag die Antwort von Robert McMahan hier am besten, da es am einfachsten zu sein scheint, gemeinsam nutzbare Include-Dateien für eines Ihrer Skripte zu verwenden. Aber es scheint einen Fehler in der Zeile zu geben if [[ -n ${variables[$argument_label]} ]], die die Nachricht "Variablen: schlechter Array-Index" auslöst. Ich habe nicht die rep zu kommentieren, und ich bezweifle , das ist die richtige ‚fix‘ , aber Einwickeln , dass ifin if [[ -n $argument_label ]] ; thensäubert sie auf.

Hier ist der Code, mit dem ich gelandet bin. Wenn Sie einen besseren Weg kennen, fügen Sie bitte einen Kommentar zu Roberts Antwort hinzu.

Include-Datei "flags-declares.sh"

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

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

Include-Datei "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;

Dein "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";
Michael
quelle
3

Wenn Sie mit Python argparse vertraut sind und es Ihnen nichts ausmacht, Python aufzurufen, um Bash-Argumente zu analysieren, gibt es einen Code, den ich als sehr hilfreich und sehr einfach zu verwenden empfand: argparse-bash https://github.com/nhoffman/ Argparse-Bash

Beispiel aus ihrem Beispiel.sh-Skript:

#!/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
quelle
2

Ich schlage eine einfache TLDR vor:; Beispiel für die nicht initiierten.

Erstellen Sie ein Bash-Skript mit dem Namen helloworld.sh

#!/bin/bash

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

echo "Hello $Name!"

Sie können dann -nbeim Ausführen des Skripts einen optionalen Parameter übergeben .

Führen Sie das Skript als solches aus:

$ bash helloworld.sh -n 'World'

Ausgabe

$ Hello World!

Anmerkungen

Wenn Sie mehrere Parameter verwenden möchten:

  1. erweitern while getops "n:" arg: domit mehr Parametern wie while getops "n:o:p:" arg: do
  2. Erweitern Sie den Fallschalter um zusätzliche Variablenzuweisungen. Wie o) Option=$OPTARGundp) Parameter=$OPTARG
pijemcolu
quelle
1
#!/bin/bash

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

Speichern Sie es als sample.sh und versuchen Sie es auszuführen

sh sample.sh -n John

in Ihrem Terminal.

Nishant Ingle
quelle
1

Ich hatte Probleme, getopts mit mehreren Flags zu verwenden, also habe ich diesen Code geschrieben. Es verwendet eine modale Variable, um Flags zu erkennen und diese Flags zu verwenden, um Variablen Argumente zuzuweisen.

Beachten Sie, dass, wenn ein Flag kein Argument haben sollte, etwas anderes als das Setzen von CURRENTFLAG ausgeführt werden kann.

    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
quelle
0

Hier ist es also meine Lösung. Ich wollte in der Lage sein, boolesche Flags ohne Bindestrich, mit einem Bindestrich und mit zwei Bindestrichen sowie mit der Zuweisung von Parametern / Werten mit einem und zwei Bindestrichen zu verarbeiten.

# 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

Einige Referenzen

  • Das Hauptverfahren wurde hier gefunden .
  • Weitere Informationen zum Übergeben aller Argumente an eine Funktion finden Sie hier .
  • Weitere Informationen zu Standardwerten finden Sie hier .
  • Weitere Infos zu declaredo $ bash -c "help declare".
  • Weitere Infos zu shiftdo $ bash -c "help shift".
H. Sánchez
quelle