Przykład użycia getopts w bash

345

Chcę wywołać myscriptplik w ten sposób:

$ ./myscript -s 45 -p any_string

lub

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

Moje wymagania to:

  • getopt tutaj, aby uzyskać argumenty wejściowe
  • sprawdź, czy -sistnieje, jeśli nie, zwróć błąd
  • sprawdź, czy wartość po -sznaku to 45 lub 90
  • sprawdź, czy -pistnieje, a po nim ciąg wejściowy
  • jeśli użytkownik wejdzie ./myscript -hlub właśnie ./myscriptwyświetli pomoc

Do tej pory wypróbowałem ten kod:

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

Ale z tym kodem dostaję błędy. Jak to zrobić za pomocą Bash i getopt?

MOHAMED
źródło
2
Opcje powinny być opcjonalne. Jeśli potrzebna wartość określoną przez -ssprawiają, że pozycyjną argumentu ./myscript 45 anystring.
chepner
@chepner$./myscript -s 45 -p any_string
MOHAMED
Jest w porządku, jeśli -pfaktycznie jest opcją (to znaczy, twój program może kontynuować, jeśli nie jest obecny). W tym wypadku ./myscript 45 -p any_string. (Myślę, że to getoptmoże obsłużyć mieszane opcje i argumenty pozycyjne, podczas gdy bashwbudowane polecenie getoptswymaga umieszczenia wszystkich argumentów pozycyjnych po opcjach.)
chepner

Odpowiedzi:

513
#!/bin/bash

usage() { echo "Usage: $0 [-s <45|90>] [-p <string>]" 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}"

Przykładowe przebiegi:

$ ./myscript.sh
Usage: ./myscript.sh [-s <45|90>] [-p <string>]

$ ./myscript.sh -h
Usage: ./myscript.sh [-s <45|90>] [-p <string>]

$ ./myscript.sh -s "" -p ""
Usage: ./myscript.sh [-s <45|90>] [-p <string>]

$ ./myscript.sh -s 10 -p foo
Usage: ./myscript.sh [-s <45|90>] [-p <string>]

$ ./myscript.sh -s 45 -p foo
s = 45
p = foo

$ ./myscript.sh -s 90 -p bar
s = 90
p = bar
Adrian Frühwirth
źródło
19
Dlaczego w wezwaniu getopts występuje wiodący dwukropek? Kiedy „h” ma za sobą dwukropek?
e40,
7
Czy usage()naprawdę powinien zwrócić 1?
Pithikos,
6
@Pithikos Dobra uwaga. Zdrowy rozsądek mówi mi, że wywołany za -hjego pośrednictwem powinien zwrócić 0, po uderzeniu w nieistniejącą flagę powinien zwrócić >0(dla uproszczenia nie rozróżniałem tych przypadków i nikt nie zmusza cię do wydrukowania tekstu użycia w drugim przypadku) . Widziałem programy, które zawsze wracają != 0, nawet w dalszym ciągu -h/--help. Może powinienem zaktualizować fragment, jeśli ludzie używają go jako szablonu (mam nadzieję, że nie)?
Adrian Frühwirth,
1
@ A.Danischewski Jest to getoptsprojekt ( '), nie ma czegoś takiego jak „opcjonalne argumenty” getopts. Analizator składni po prostu nie -pmoże ustalić , czy następny token jest argumentem dla bieżącej opcji, czy też samą opcją, ponieważ może być zamierzoną wartością. Państwo może włamać się wokół tego, jeśli absolutnie wiedzieć, że parametr opcja nie może wyglądać innej ważnej opcji, tak, ale można powiedzieć, że jest powód opcjonalne argumenty nie są zdefiniowane w POSIX.
Adrian Frühwirth
4
@ user1011471 Masz rację! Nawiasy klamrowe, że tak powiem, tylko pomagają bashleksykowi w identyfikowaniu zmiennych. W wielu przypadkach są niepotrzebne, a fakt, że zawsze ich używam, jest kwestią osobistego stylu kodowania. Dla mnie łatwiej (i ładniej) jest po prostu zawsze ich używać zamiast zapamiętywać zasady analizowania w odniesieniu do dwuznaczności. Prawie to samo, dlaczego ktoś pisałby if (foo) { bar; }zamiast if (foo) bar;w języku C (estetyka i / lub unikanie głupich błędów).
Adrian Frühwirth,
109

Problem z oryginalnym kodem polega na tym, że:

  • h: oczekuje parametru tam, gdzie nie powinien, więc zmień go na just h (bez dwukropka)
  • aby się spodziewać -p any_string, musisz dodać p:do listy argumentów

Zasadniczo :po opcji oznacza, że ​​wymaga argumentu.


Podstawowa składnia getoptsto (patrz man bash:):

getopts OPTSTRING VARNAME [ARGS...]

gdzie:

  • OPTSTRING jest łańcuchem z listą oczekiwanych argumentów,

    • h- sprawdź opcję -h bez parametrów; podaje błąd w nieobsługiwanych opcjach;
    • h:- sprawdź opcję za -h pomocą parametrem; podaje błędy w nieobsługiwanych opcjach;
    • abc- Sprawdź opcje -a, -b, -c; podaje błędy w nieobsługiwanych opcjach;
    • :abc- Sprawdź opcje -a, -b, -c; cisze błędy w nieobsługiwanych opcjach;

      Uwagi: Innymi słowy, dwukropek przed opcjami umożliwia obsługę błędów w kodzie. Zmienna będzie zawierać ?w przypadku nieobsługiwanej opcji, :w przypadku braku wartości.

  • OPTARG - jest ustawiony na bieżącą wartość argumentu,

  • OPTERR - wskazuje, czy Bash powinien wyświetlać komunikaty o błędach.

Tak więc kod może być:

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

Przykładowe użycie:

$ ./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

Zobacz: Mały poradnik getopts na Bash Hackers Wiki

kenorb
źródło
2
Zmienić funkcję wykorzystania do tego: usage() { echo "$0 usage:" && grep "[[:space:]].)\ #" $0 | sed 's/#//' | sed -r 's/([a-z])\)/-\1/'; exit 0; }. Uwzględnia tylko jeden znak spacji przed opcją litery, usuwa znak # z komentarza i wstawia znak „-” przed opcją litery, dzięki czemu jest bardziej zrozumiały dla polecenia.
Poagester,
2
@kenorb: Dwukropek przed opcjami nie ignoruje nieobsługiwanych opcji, ale wycisza błędy z bash i pozwala obsłużyć go w kodzie. Zmienna będzie zawierać „?” w przypadku nieobsługiwanej opcji i „:” w przypadku brakującej wartości.
Hynek-Pichi- Vychodil
1
Dzięki za szczegółowe dokumenty, nie byłem w stanie uzyskać :właściwego prawa, dopóki nie zobaczyłem tych notatek. Musimy dodać a :do opcji, w których oczekujemy argumentu.
Aukhan
51

Posługiwać się getopt

Dlaczego getopt?

Aby przeanalizować złożone argumenty wiersza polecenia, aby uniknąć zamieszania i wyjaśnić opcje, które analizujemy, aby czytelnik poleceń mógł zrozumieć, co się dzieje.

Co to jest getopt?

getoptsłuży do dzielenia (parsowania) opcji w wierszach poleceń w celu łatwego parsowania według procedur powłoki oraz do sprawdzania legalnych opcji. Korzysta z GNUgetopt(3) procedury .

getopt może mieć następujące typy opcji.

  1. Opcje bez wartości
  2. opcje pary klucz-wartość

Uwaga: W tym dokumencie podczas wyjaśniania składni:

  • Wszystko wewnątrz [] jest opcjonalnym parametrem w składni / przykładach.
  • to symbol zastępczy, co oznacza, że ​​należy go zastąpić rzeczywistą wartością.

JAK KORZYSTAĆ getopt?

Składnia: pierwsza forma

getopt optstring parameters

Przykłady:

# This is correct
getopt "hv:t::" "-v 123 -t123"  
getopt "hv:t::" "-v123 -t123"  # -v and 123 doesn't have whitespace

# -h takes no value.
getopt "hv:t::" "-h -v123"


# This is wrong. after -t can't have whitespace.
# Only optional params cannot have whitespace between key and value
getopt "hv:t::" "-v 123 -t 123"

# Multiple arguments that takes value.
getopt "h:v:t::g::" "-h abc -v 123 -t21"

# Multiple arguments without value
# All of these are correct
getopt "hvt" "-htv"
getopt "hvt" "-h -t -v"
getopt "hvt" "-tv -h"

Tutaj h, v, t to opcje, a -h -v -t to sposób, w jaki opcje powinny być podawane w wierszu poleceń.

  1. „h” jest opcją bez wartości.
  2. „v:” oznacza, że ​​opcja -v ma wartość i jest opcją obowiązkową. „:” oznacza ma wartość.
  3. „t ::” oznacza, że ​​opcja -t ma wartość, ale jest opcjonalna. „::” oznacza opcjonalne.

W opcjonalnym parametrze wartość nie może mieć separacji białych znaków z opcją. Zatem w przykładzie „-t123” -t to opcja 123 to wartość.

Składnia: druga forma

getopt [getopt_options] [--] [optstring] [parameters]

Tutaj po getopt podzielony jest na pięć części

  • Samo polecenie, tj. Getopt
  • W getopt_options opisano, jak parsować argumenty. pojedyncze długie kreski, opcje podwójne kreski.
  • -, oddziela getopt_options od opcji, które chcesz przeanalizować i dozwolonych krótkich opcji
  • Krótkie opcje są brane natychmiast po znalezieniu. Podobnie jak pierwsza składnia Form.
  • Parametry to opcje przekazane do programu. Opcje, które chcesz przeanalizować i uzyskać ustawione na nich rzeczywiste wartości.

Przykłady

getopt -l "name:,version::,verbose" -- "n:v::V" "--name=Karthik -version=5.2 -verbose"

Składnia: trzecia forma

getopt [getopt_options] [-o options] [--] [optstring] [parameters]

Tutaj po getopt podzielony jest na pięć części

  • Samo polecenie, tj. Getopt
  • W getopt_options opisano, jak parsować argumenty. pojedyncze długie kreski, opcje podwójne kreski.
  • Krótkie opcje tj. -O lub --opcje. Podobnie jak pierwsza składnia Form, ale z opcją „-o” i przed „-” (podwójny myślnik).
  • -, oddziela getopt_options od opcji, które chcesz przeanalizować i dozwolonych krótkich opcji
  • Parametry to opcje przekazane do programu. Opcje, które chcesz przeanalizować i uzyskać ustawione na nich rzeczywiste wartości.

Przykłady

getopt -l "name:,version::,verbose" -a -o "n:v::V" -- "-name=Karthik -version=5.2 -verbose"

GETOPT_OPTIONS

getopt_options zmienia sposób, w jaki analizowane są parametry wiersza poleceń.

Poniżej znajdują się niektóre z opcji getopt_options

Opcja: -l lub --długie opcje

Oznacza, że ​​polecenie getopt powinno umożliwiać rozpoznanie opcji wieloznakowych. Wiele opcji oddzielonych jest przecinkiem.

Na przykład --name=Karthikjest długą opcją wysyłaną w wierszu polecenia. W getopt użycie długich opcji jest podobne

getopt "name:,version" "--name=Karthik"

Ponieważ nazwa: jest określona, ​​opcja powinna zawierać wartość

Opcja: -a lub - alternatywna

Oznacza, że ​​polecenie getopt powinno pozwolić na długą opcję posiadania pojedynczego myślnika „-” zamiast podwójnego myślnika „-”.

Przykład, zamiast --name=Karthikmożesz użyć po prostu-name=Karthik

getopt "name:,version" "-name=Karthik"

Kompletny przykład skryptu z kodem:

#!/bin/bash

# filename: commandLine.sh
# author: @theBuzzyCoder

showHelp() {
# `cat << EOF` This means that cat should stop reading when EOF is detected
cat << EOF  
Usage: ./installer -v <espo-version> [-hrV]
Install Pre-requisites for EspoCRM with docker in Development mode

-h, -help,          --help                  Display help

-v, -espo-version,  --espo-version          Set and Download specific version of EspoCRM

-r, -rebuild,       --rebuild               Rebuild php vendor directory using composer and compiled css using grunt

-V, -verbose,       --verbose               Run script in verbose mode. Will print out each step of execution.

EOF
# EOF is found above and hence cat command stops reading. This is equivalent to echo but much neater when printing out.
}


export version=0
export verbose=0
export rebuilt=0

# $@ is all command line parameters passed to the script.
# -o is for short options like -v
# -l is for long options with double dash like --version
# the comma separates different long options
# -a is for long options with single dash like -version
options=$(getopt -l "help,version:,verbose,rebuild,dryrun" -o "hv:Vrd" -a -- "$@")

# set --:
# If no arguments follow this option, then the positional parameters are unset. Otherwise, the positional parameters 
# are set to the arguments, even if some of them begin with a ‘-’.
eval set -- "$options"

while true
do
case $1 in
-h|--help) 
    showHelp
    exit 0
    ;;
-v|--version) 
    shift
    export version=$1
    ;;
-V|--verbose)
    export verbose=1
    set -xv  # Set xtrace and verbose mode.
    ;;
-r|--rebuild)
    export rebuild=1
    ;;
--)
    shift
    break;;
esac
shift
done

Uruchamianie tego pliku skryptu:

# With short options grouped together and long option
# With double dash '--version'

bash commandLine.sh --version=1.0 -rV
# With short options grouped together and long option
# With single dash '-version'

bash commandLine.sh -version=1.0 -rV

# OR with short option that takes value, value separated by whitespace
# by key

bash commandLine.sh -v 1.0 -rV

# OR with short option that takes value, value without whitespace
# separation from key.

bash commandLine.sh -v1.0 -rV

# OR Separating individual short options

bash commandLine.sh -v1.0 -r -V
theBuzzyCoder
źródło
źródło: linkedin.com/pulse/…
theBuzzyCoder
getopt vs getopts .. zupełnie inna zgodność między platformami
shadowbq
35

Przykład zapakowany w getopt(moja dystrybucja /usr/share/getopt/getopt-parse.bash), wygląda tak, jakby obejmował wszystkie twoje przypadki:

#!/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
Brian Cain
źródło
11
Zewnętrzne polecenie getopt (1) nigdy nie jest bezpieczne w użyciu, chyba że wiesz, że jest to GNU getopt, wywołujesz je w sposób specyficzny dla GNU i upewniasz się, że GETOPT_COMPATIBLE nie znajduje się w środowisku. Zamiast tego należy użyć getopts (wbudowana powłoka) lub po prostu zapętlić parametry pozycyjne.
Gilles Quenot
@sputnick, tyvm, nie wiedział o tym.
Brian Cain
14
Eh, żadne zewnętrzne polecenie nie jest bezpieczne w użyciu według tego standardu. Wbudowane getopts brakuje kluczowych funkcji, a jeśli chcesz sprawdzić GETOPT_COMPATIBLE, jest to łatwiejsze niż przeniesienie funkcji getopt.
Michael Terry
12

Wiem, że już na to odpowiedziano, ale dla nagrania i dla każdego, kto ma takie same wymagania jak ja, postanowiłem opublikować tę powiązaną odpowiedź. Kod jest zalewany komentarzami wyjaśniającymi kod.

Zaktualizowana odpowiedź:

Zapisz plik jako 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
}

Następnie możesz użyć tego w następujący sposób:

#!/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}

Stara odpowiedź:

Niedawno musiałem zastosować ogólne podejście. Natknąłem się na to rozwiązanie:

#!/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}

Nie przetestowałem tego z grubsza, więc mogłem mieć tam kilka błędów.

Sebastian
źródło
1
Jeśli używasz getoptsfunkcji, dodaj local OPTIND OPTARGdo funkcji
glenn jackman
@glennjackman faktycznie bardziej przypomina podejście sed, niż używaniegetopts
Sebastian
8

Przykład POSIX 7

Warto również sprawdzić przykład ze standardu: 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" "$*"

A potem możemy to wypróbować:

$ 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

Testowany w Ubuntu 17.10, shjest kreską 0.5.8.

Ciro Santilli
źródło
0

„getops” i „getopt” są bardzo ograniczone. Chociaż „getopt” sugeruje się, aby w ogóle nie był używany, oferuje długie opcje. Gdzie jako „getopts” dopuszcza tylko opcje pojedynczych znaków, takie jak „-a” „-b”. Istnieje kilka innych wad podczas korzystania z jednego z nich.

Napisałem więc mały skrypt, który zastępuje „getopts” i „getopt”. To początek, prawdopodobnie można by go znacznie poprawić.

Aktualizacja 08-04-2020 : Dodałem obsługę łączników, np. „--Package-name”.

Sposób użycia: „./script.sh instalacja pakietu - pakiet” nazwa ze spacją ”- build --archive”

# Example:
# parseArguments "${@}"
# echo "${ARG_0}" -> package
# echo "${ARG_1}" -> install
# echo "${ARG_PACKAGE}" -> "name with space"
# echo "${ARG_BUILD}" -> 1 (true)
# echo "${ARG_ARCHIVE}" -> 1 (true)
function parseArguments() {
  PREVIOUS_ITEM=''
  COUNT=0
  for CURRENT_ITEM in "${@}"
  do
    if [[ ${CURRENT_ITEM} == "--"* ]]; then
      printf -v "ARG_$(formatArgument "${CURRENT_ITEM}")" "%s" "1" # could set this to empty string and check with [ -z "${ARG_ITEM-x}" ] if it's set, but empty.
    else
      if [[ $PREVIOUS_ITEM == "--"* ]]; then
        printf -v "ARG_$(formatArgument "${PREVIOUS_ITEM}")" "%s" "${CURRENT_ITEM}"
      else
        printf -v "ARG_${COUNT}" "%s" "${CURRENT_ITEM}"
      fi
    fi

    PREVIOUS_ITEM="${CURRENT_ITEM}"
    (( COUNT++ ))
  done
}

# Format argument.
function formatArgument() {
  ARGUMENT="${1^^}" # Capitalize.
  ARGUMENT="${ARGUMENT/--/}" # Remove "--".
  ARGUMENT="${ARGUMENT//-/_}" # Replace "-" with "_".
  echo "${ARGUMENT}"
}
Twoez
źródło