591 Stimmen

Ein Beispiel dafür, wie man getopts in bash verwendet

Ich möchte die Datei myscript auf diese Weise aufrufen:

$ ./myscript -s 45 -p beliebiger_string

oder

$ ./myscript -h  # Soll Hilfe anzeigen
$ ./myscript     # Soll Hilfe anzeigen

Meine Anforderungen sind:

  • getopt hier, um die Eingabeargumente zu erhalten
  • Überprüfen, ob -s existiert, falls nicht eine Fehlermeldung ausgeben
  • Überprüfen, ob der Wert nach dem -s 45 oder 90 ist
  • Überprüfen, ob -p existiert und ob danach ein Eingabestring vorhanden ist
  • Wenn der Benutzer ./myscript -h oder einfach nur ./myscript eingibt, dann Hilfe anzeigen

Ich habe bisher diesen Code versucht:

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

Aber mit diesem Code erhalte ich Fehler. Wie kann ich das mit Bash und getopt machen?

13voto

Sebastian Punkte 3696

Ich weiß, dass dies bereits beantwortet wurde, aber der Vollständigkeit halber und für alle mit denselben Anforderungen wie ich habe ich beschlossen, diese verwandte Antwort zu veröffentlichen. Der Code ist mit Kommentaren übersät, um den Code zu erklären.

Aktualisierte Antwort:

Speichern Sie die Datei als 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 "Verwendung:\n\t%s\n" "${0} ${OPT_DESC}"
    exit 10
}

Dann können Sie es so verwenden:

#!/bin/bash
#
# [ und ] definieren optionale Argumente
#

# Speicherort der getopts.sh Datei
quelle ./getopt.sh
USAGE="-u BENUTZER -d DATENBANK -p PASS -s SID [ -a START_DATUM_ZEIT ]"
parse_options "${USAGE}" ${@}

echo ${BENUTZER}
echo ${START_DATUM_ZEIT}

Alte Antwort:

Ich musste kürzlich einen generischen Ansatz verwenden. Ich stieß auf diese Lösung:

#!/bin/bash
# Option Beschreibung:
# -------------------
#
# Die Option Beschreibung basiert auf dem eingebauten getopts-Befehl von bash. Die Beschreibung fügt eine Funktion zur Variablennamen hinzu, die in Zukunft zur Prüfung von erforderlichen oder optionalen Werten verwendet werden kann.
# Die Option Beschreibung fügt den String "=>VARIABLE_NAME" hinzu. Der Variablenname sollte GROßBUCHSTABEN sein. Gültige Zeichen sind [A-Z_]*.
#
# Ein Beispiel für eine Optionsbeschreibung:
#   OPT_DESC="a:=>A_VARIABLE|b:=>B_VARIABLE|c=>C_VARIABLE"
#
# -a-Option erfordert einen Wert (das Doppelpunkt bedeutet das) und sollte in der Variablen A_VARIABLE gespeichert werden.
# "|" wird verwendet, um die Optionsbeschreibung zu trennen.
# Die Regel der -b-Option ist dieselbe wie bei -a.
# Die -c-Option erfordert keinen Wert (das Fehlen des Doppelpunkts bedeutet dies) und ihre Existenz sollte in C_VARIABLE festgelegt werden.
#
#   ~$ echo get_options ${OPT_DESC}
#   a:b:c
#   ~$
#

# Erforderliche Optionen
REQUIRED_DESC="a:=>REQ_A_VAR_VALUE|B:=>REQ_B_VAR_VALUE|c=>REQ_C_VAR_FLAG"

# Optionale Optionen (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
}

# Hilfsfunktion, die die Optionen characters zurückgibt, die an 'getopts' aus einer Optionsbeschreibung übergeben werden sollen.
# Argumente:
#   $1: Die Optionenbeschreibung (SIEHE OBEN)
#
# Beispiel:
#   OPT_DESC="h:=>H_VAR|f:=>F_VAR|P=>P_VAR|W=>W_VAR"
#   OPTIONS=$(get_options ${OPT_DESC})
#   echo "${OPTIONS}"
#
# Ausgang:
#   "h:f:PW"
function get_options {
    echo ${1} | sed -e "s/\([a-zA-Z]\:\?\)=>[A-Z_]*|\?/\1/g"
}

# Hilfsfunktion, die alle Variablennamen durch '|' getrennt zurückgibt
# Argumente:
#       $1: Die Optionenbeschreibung (SIEHE OBEN)
#
# Beispiel:
#       OPT_DESC="h:=>H_VAR|f:=>F_VAR|P=>P_VAR|W=>W_VAR"
#       VARNAMES=$(get_values ${OPT_DESC})
#       echo "${VARNAMES}"
#
# Ausgang:
#       "H_VAR|F_VAR|P_VAR|W_VAR"
function get_variables {
    echo ${1} | sed -e "s/[a-zA-Z]\:\?=>\([^|]*\)/\1/g"
}

# Hilfsfunktion, die den Variablennamen basierend auf dem
# übergebenen Option den Variablennamen zurückgibt.
# Argumente:
#   $1: Die Optionenbeschreibung (SIEHE OBEN)
#   $2: Die Option, für die der Variablenname abgerufen werden soll
#
# Beispiel:
#   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}"
#
# Ausgang:
#   "H_VAR"
function get_variable_name {
    VAR=$(echo ${1} | sed -e "s/.*${2}\:\?=>\([^|]*\).*/\1/g")
    if [[ ${VAR} == ${1} ]]; then
        echo ""
    else
        echo ${VAR}
    fi
}

# Holt die erforderlichen Optionen aus der erforderlichen Beschreibung
REQUIRED=$(get_options ${REQUIRED_DESC})

# Holt die optionalen Optionen (duh) aus der optionalen Beschreibung
OPTIONAL=$(get_options ${OPTIONAL_DESC})

# oder... $(get_options "${OPTIONAL_DESC}|${REQUIRED_DESC}")

# Der Doppelpunkt am Anfang weist getopts an, still zu bleiben
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))

# Überprüft erforderliche Optionen. Gibt einen Fehler aus und beendet, falls
# erforderliche Optionen fehlen.

# Verwendung der Funktionsversion ...
VARS=$(get_variables ${REQUIRED_DESC})
IFS="|"
for VARNAME in $VARS;
do
    [[ -v ${VARNAME} ]] || usage
done
unset IFS

# ... oder Verwendung der IFS-Version (ohne Funktion)
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}

Ich habe dies nicht gründlich getestet, daher könnten einige Fehler darin sein.

7voto

Twoez Punkte 540

getopts und getopt sind sehr eingeschränkt. Während empfohlen wird, getopt überhaupt nicht zu verwenden, bietet es jedoch Langoptionen an. Wohingegen getopts nur einzeilige Optionen wie -a -b zulässt. Es gibt noch einige weitere Nachteile bei der Verwendung von beiden.

Also habe ich ein kleines Skript geschrieben, das getopts und getopt ersetzt. Es ist ein Anfang, es könnte wahrscheinlich noch viel verbessert werden.

Aktualisierung 08-04-2020: Ich habe Unterstützung für Bindestriche hinzugefügt, z.B. --package-name.

Verwendung: "./script.sh package install --package "Name mit Leerzeichen" --build --archive"

# Beispiel:
# parseArguments "${@}"
# echo "${ARG_0}" -> package
# echo "${ARG_1}" -> install
# echo "${ARG_PACKAGE}" -> "Name mit Leerzeichen"
# echo "${ARG_BUILD}" -> 1 (true)
# echo "${ARG_ARCHIVE}" -> 1 (true)
function parseArguments() {
  VORHERIGES_ELEMENT=''
  ZÄHLER=0
  for AKTUELLES_ELEMENT in "${@}"
  do
    if [[ ${AKTUELLES_ELEMENT} == "--"* ]]; then
      printf -v "ARG_$(formatArgument "${AKTUELLES_ELEMENT}")" "%s" "1" # könnte dies auf einen leeren String setzen und mit [ -z "${ARG_ELEMENT-x}" ] überprüfen, ob es gesetzt ist, aber leer ist.
    else
      if [[ $VORHERIGES_ELEMENT == "--"* ]]; then
        printf -v "ARG_$(formatArgument "${VORHERIGES_ELEMENT}")" "%s" "${AKTUELLES_ELEMENT}"
      else
        printf -v "ARG_${ZÄHLER}" "%s" "${AKTUELLES_ELEMENT}"
      fi
    fi

    VORHERIGES_ELEMENT="${AKTUELLES_ELEMENT}"
    (( ZÄHLER++ ))
  done
}

# Argument formatieren.
function formatArgument() {
  ARGUMENT="${1^^}" # Großschreibung.
  ARGUMENT="${ARGUMENT/--/}" # Entferne "--".
  ARGUMENT="${ARGUMENT//-/_}" # Ersetze "-" durch "_".
  echo "${ARGUMENT}"
}

2voto

Greenonline Punkte 1275

Das riesige Einzeilige in Mark G.s Kommentar (unter Adrian Frühwirths Antwort) in eine lesbarere Antwort umwandeln - dies zeigt, wie man vermeidet, getopts zu verwenden, um optionale Argumente zu erhalten:

usage() { 
    printf "Verwendung: %s  [<-s|--sopt> <45|90>] [<-p|--popt> ]\n" "$0"; 
    return 1; 
}; 

main() { 
    req="${1:?$(usage)}";
    shift; 
    s="";
    p="";
    while [ "$#" -ge 1 ]; do
        case "$1" in 
            -s|--sopt) 
                shift;
                s="${1:?$(usage)}";
                [ "$s" -eq 45 ] || [ "$s" -eq 90 ] || { 
                    usage; 
                    return 1; 
                } 
                ;; 
            -p|--popt) 
                shift; 
                p="${1:?$(usage)}" 
                ;; 
            *) 
                usage;
                return 1 
                ;; 
        esac; 
        shift;
    done; 
    printf "req = %s\ns = %s\np = %s\n" "$req" "$s" "$p"; 
};

main "$@"

Wie in n.caillous Kommentar erwähnt:

es wird fehlschlagen, wenn es keinen Leerzeichen zwischen den Optionen und deren Argument gibt.

Um es jedoch POSIX-konform zu machen (aus Mark G.s anderem Kommentar):

        case "$1" in 
            -s*)
                s=${1#-s}; 
                if [ -z "$s" ]; 
                    shift; 
                    s=$1; 
                fi

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X