Пример использования getopts в bash

215

Я хочу вызвать myscript файл следующим образом:

$ ./myscript -s 45 -p any_string

или

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

Мои требования:

  • getopt здесь, чтобы получить входные аргументы
  • проверьте, что -s существует, если не возвращать ошибку
  • убедитесь, что значение после -s равно 45 или 90
  • убедитесь, что существует -p и есть строка ввода после
  • если пользователь вводит ./myscript -h или просто ./myscript, тогда отобразите справку

Я пробовал до сих пор этот код:

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

Но с этим кодом я получаю ошибки. Как это сделать с помощью Bash и getopt?

  • 2
    Опции должны быть необязательными. Если вам требуется значение, указанное в -s , сделайте его позиционным аргументом: ./myscript 45 anystring .
  • 0
    @chepner $./myscript -s 45 -p any_string
Показать ещё 1 комментарий
Теги:
getopts

5 ответов

373
Лучший ответ
#!/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}"

Пример выполнения:

$ ./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
  • 12
    В вызове getopts почему ведущий двоеточие? Когда после h стоит двоеточие?
  • 0
    @ e40 Хорошо, спасибо! Вероятно, ошибка копирования-вставки на h . Начальная двоеточие объясняется в getopts странице для getopts . Попробуйте $ ./myscript.sh -s 45 -p с и без него, и разница будет очевидна. Хотите ли вы использовать эту функцию, решать только вам и как вы хотите справиться со всем этим.
Показать ещё 19 комментариев
47

Проблема с исходным кодом заключается в следующем:

  • h: ожидает параметр, где он не должен, поэтому измените его на h (без двоеточия)
  • ожидать -p any_string, вам нужно добавить p: в список аргументов

Основной синтаксис getopts равен (см. man bash):

getopts OPTSTRING VARNAME [ARGS...]

где:

  • OPTSTRING - строка со списком ожидаемых аргументов,

    • h - проверьте параметры -h без; дает ошибку при неподдерживаемых опциях;
    • h: - проверьте параметр -h с параметром; дает ошибки в неподдерживаемых опциях;
    • abc - проверьте параметры -a, -b, -c; дает ошибки в неподдерживаемых опциях;
    • :abc - проверьте параметры -a, -b, -c; отключает ошибки в неподдерживаемых опциях;

      Примечания: Другими словами, двоеточие перед параметрами позволяет обрабатывать ошибки в коде. Переменная будет содержать ? в случае неподдерживаемой опции, : в случае отсутствия значения.

  • OPTARG - установлено значение текущего аргумента,

  • OPTERR - указывает, должен ли Bash отображать сообщения об ошибках.

Таким образом, код может быть:

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

Пример использования:

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

Смотрите: Небольшой учебник по учебникам в Bash Hackers Wiki

  • 2
    Измените функцию использования следующим образом: usage() { echo "$0 usage:" && grep "[[:space:]].)\ #" $0 | sed 's/#//' | sed -r 's/([az])\)/-\1/'; exit 0; } Он учитывает только один символ пробела перед параметром буквы, удаляет знак # из комментария и добавляет символ «-» перед параметром буквы, делая его более понятным для команды.
  • 1
    @kenorb: двоеточие перед опциями не игнорирует неподдерживаемые опции, но заставляет глушить ошибки из bash и позволяет обрабатывать их в вашем коде. Переменная будет содержать '?' в случае неподдерживаемой опции и «:» в случае пропущенного значения.
Показать ещё 1 комментарий
28

Пример, упакованный в getopt (мой дистрибутив помещается в /usr/share/getopt/getopt-parse.bash), выглядит так, как будто он охватывает все ваши случаи:

#!/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
  • 11
    Внешнюю команду getopt (1) никогда нельзя использовать безопасно, если только вы не знаете, что это GNU getopt, вы вызываете ее специфичным для GNU способом и гарантируете, что GETOPT_COMPATIBLE не находится в среде. Вместо этого используйте getopts (встроенную оболочку) или просто переключайтесь между позиционными параметрами.
  • 0
    @sputnick, tyvm, не знал этого.
Показать ещё 1 комментарий
9

Я знаю, что об этом уже ответили, но для записи и для тех, кто с теми же запросами, что и я, я решил опубликовать этот ответ. Код будет заполнен комментариями для объяснения кода.

Обновленный ответ:

Сохраните файл как 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
}

Затем вы можете использовать его следующим образом:

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

Старый ответ:

Недавно мне нужно было использовать общий подход. Я столкнулся с этим решением:

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

Я не тестировал это примерно так, поэтому у меня могли быть некоторые ошибки.

2

Пример POSIX 7

Также стоит проверить пример со стандартом: 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" "$*"

И тогда мы можем попробовать:

$ 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

Протестировано в Ubuntu 17.10, sh - тире 0.5.8.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню