6

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

31

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

$ ./myscript -s 45 -p any_string

или

$ ./myscript -h  # должен показать помощь
$ ./myscript     # должен показать помощь

Мои требования следующие:

  • Использовать getopts для получения входных аргументов
  • Проверить, существует ли -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?

5 ответ(ов)

7

Ваш скрипт на bash предназначен для обработки аргументов командной строки с использованием getopts. Давайте разберем, как он работает, и приведем примеры использования.

Функция usage выводит сообщение о том, как правильно использовать скрипт, и завершает его с кодом ошибки 1.

Цикл while getopts ":s:p:" o; do позволяет скрипту принимать два параметра:

  • -s, который ожидает аргумент, значение которого должно быть либо 45, либо 90.
  • -p, который также ожидает строковый аргумент.

Если передан недопустимый аргумент, вызывается функция usage. После обработки параметров с помощью getopts, скрипт проверяет, заданы ли оба параметра -s и -p. Если хотя бы один из них не задан, также вызывается функция usage.

Далее, если параметры заданы правильно, скрипт выводит их значения.

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

  1. Если запустить скрипт без параметров:

    $ ./myscript.sh
    Usage: ./myscript.sh [-s <45|90>] [-p <string>]
    
  2. Если вызвать справку с параметром -h:

    $ ./myscript.sh -h
    Usage: ./myscript.sh [-s <45|90>] [-p <string>]
    
  3. Если передать пустые значения:

    $ ./myscript.sh -s "" -p ""
    Usage: ./myscript.sh [-s <45|90>] [-p <string>]
    
  4. Если передать недопустимое значение для параметра -s:

    $ ./myscript.sh -s 10 -p foo
    Usage: ./myscript.sh [-s <45|90>] [-p <string>]
    
  5. Если использовать допустимые значения:

    $ ./myscript.sh -s 45 -p foo
    s = 45
    p = foo
    
  6. И еще один пример с другим значением -s:

    $ ./myscript.sh -s 90 -p bar
    s = 90
    p = bar
    

Этот скрипт является хорошим примером обработки аргументов командной строки в bash и проверок на правильность введенных значений. Если у вас есть дополнительные вопросы, не стесняйтесь их задавать!

2

Почему использовать getopt?

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

Что такое getopt?

getopt используется для разбора (парсинга) опций в командных строках, что облегчает их обработку программами оболочки и проверку на допустимые опции. Он использует процедуры GNU getopt(3) для выполнения этой задачи.

getopt может иметь следующие типы опций:

  1. Опции без значений
  2. Опции в паре ключ-значение

Примечание: В этом документе при объяснении синтаксиса:

  • Всякое, что находится внутри [ ], — это необязательный параметр в синтаксисе/примерах.
  • <value> — это заполнителя, который означает, что его следует заменить на фактическое значение.

КАК ИСПОЛЬЗОВАТЬ getopt?

Синтаксис: Первая форма

getopt optstring parameters

Примеры:

# Это правильно
getopt "hv:t::" -v 123 -t123  
getopt "hv:t::" -v123 -t123  # -v и 123 не имеют пробелов

# -h не принимает значение.
getopt "hv:t::" -h -v123

# Это неправильно. После -t не может быть пробела.
# Только необязательные параметры не могут иметь пробел между ключом и значением
getopt "hv:t::" -v 123 -t 123

# Несколько аргументов, которые принимают значения.
getopt "h:v:t::g::" -h abc -v 123 -t21

# Несколько аргументов без значений
# Все из этих примеров правильные
getopt "hvt" -htv
getopt "hvt" -h -t -v
getopt "hvt" -tv -h

Здесь h, v и t — опции, а -h -v -t — это то, как опции должны быть указаны в командной строке.

  1. 'h' — это опция без значений.
  2. 'v:' говорит о том, что опция -v имеет значение и является обязательной. : означает, что имеет значение.
  3. 't::' подразумевает, что опция -t имеет значение, но является необязательной. :: означает необязательную.

В необязательном параметре значение не может иметь разделителя в виде пробела с опцией. Таким образом, в примере -t123 опция -t — это 123 значение.

Синтаксис: Вторая форма

getopt [getopt_options] [--] optstring parameters

Здесь getopt разбивается на пять частей:

  • Команда, т.е. getopt.
  • getopt_options — описывает, как парсить аргументы: одиночные тире для длинных опций, двойное тире для опций.
  • -- отделяет getopt_options от опций, которые вы хотите разобрать и разрешенных коротких опций.
  • Короткие опции, которые принимаются сразу после нахождения --, как в синтаксисе первой формы.
  • Параметры — это опции, которые вы передали в программу. Опции, которые вы хотите разобрать и получить фактические значения, установленные на них.

Пример

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

Синтаксис: Третья форма

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

Здесь getopt также разбивается на пять частей:

  • Команда, т.е. getopt.
  • getopt_options, описывающие, как парсить аргументы: одиночные тире для длинных опций, двойные тире.
  • Короткие опции, т.е. -o или --options. Как в первой форме, но с опцией "-o" и до -- (двойное тире).
  • --, отделяет getopt_options от опций, которые вы хотите разобрать.
  • Параметры — это опции, которые вы передали в программу.

Пример

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

GETOPT_OPTIONS

getopt_options изменяет способ разбора параметров командной строки.

Вот некоторые из getopt_options:

Опция: -l или --longoptions

Значит, команда getopt должна разрешать многосимвольные опции. Несколько опций разделяются запятыми.

Например, --name=Karthik — это длинная опция, переданная в командной строке. В getopt использование длинных опций выглядит так:

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

Так как name: указано, опция должна содержать значение.

Опция: -a или --alternative

Значит, команда getopt должна разрешать длинной опции иметь одиночное тире '-', а не двойное '--'.

Например, вместо --name=Karthik вы могли бы использовать просто -name=Karthik.

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

Пример полного скрипта с кодом:

#!/bin/bash

# filename: commandLine.sh
# author: @theBuzzyCoder

showHelp() {
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
}

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

options=$(getopt -l "help,version:,verbose,rebuild,dryrun" -o "hv:Vrd" -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  # Включаем подробный режим.
    ;;
-r|--rebuild)
    export rebuilt=1
    ;;
--)
    shift
    break;;
esac
shift
done

Запуск этого скрипта:

# С короткими опциями, группированными вместе, и длинной опцией
# С двойным тире '--version'

bash commandLine.sh --version=1.0 -rV
# С короткими опциями, группированными вместе, и длинной опцией
# С одиночным тире '-version'

bash commandLine.sh -version=1.0 -rV

# ИЛИ с короткой опцией, которая принимает значение, значение отделяется пробелом
# от ключа

bash commandLine.sh -v 1.0 -rV

# ИЛИ с короткой опцией, которая принимает значение, значение без пробела
# отделения от ключа.

bash commandLine.sh -v1.0 -rV

# ИЛИ разделяя индивидуальные короткие опции

bash commandLine.sh -v1.0 -r -V
0

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

#!/bin/bash

# Небольшая примерная программа для использования новой программы getopt(1).
# Эта программа будет работать только с bash(1).
# Аналогичная программа на языке скриптов tcsh(1) доступна
# как parse.tcsh.

# Пример входных и выходных данных (из командной строки bash):
# ./parse.bash -a par1 'another arg' --c-long 'wow!*\?' -cmore -b " very long "
# Опция a
# Опция c, без аргумента
# Опция c, аргумент 'more'
# Опция b, аргумент ' very long '
# Остальные аргументы:
# --> 'par1'
# --> 'another arg'
# --> 'wow!*\?'

# Обратите внимание, что мы используем `"$@"`, чтобы каждый параметр командной строки
# расширялся в отдельное слово. Кавычки вокруг '$@' являются обязательными!
# Нам нужен TEMP, так как `eval set --` уничтожит возвращаемое значение getopt.
TEMP=$(getopt -o ab:c:: --long a-long,b-long:,c-long:: \
              -n 'example.bash' -- "$@")

if [ $? != 0 ] ; then echo "Завершение..." >&2 ; exit 1 ; fi

# Обратите внимание на кавычки вокруг '$TEMP': они обязательны!
eval set -- "$TEMP"

while true ; do
    case "$1" in
        -a|--a-long) echo "Опция a" ; shift ;;
        -b|--b-long) echo "Опция b, аргумент '$2'" ; shift 2 ;;
        -c|--c-long) 
            # c имеет необязательный аргумент. Поскольку мы находимся в кавычках,
            # пустой параметр будет сгенерирован, если его необязательный
            # аргумент не найден.
            case "$2" in
                "") echo "Опция c, без аргумента"; shift 2 ;;
                *)  echo "Опция c, аргумент '$2'" ; shift 2 ;;
            esac ;;
        --) shift ; break ;;
        *) echo "Внутренняя ошибка!" ; exit 1 ;;
    esac
done
echo "Остальные аргументы:"
for arg do echo '--> '"'$arg'" ; done

Этот скрипт показывает, как можно использовать getopt для обработки опций и аргументов командной строки в bash. Обратите внимание на важность кавычек при работе с параметрами, чтобы избежать ошибок при их разборе.

0

Вот перевод примера из стандарта POSIX 7:

aflag=
bflag=
while getopts ab: name
do
    case $name in
    a)    aflag=1;;
    b)    bflag=1
          bval="$OPTARG";;
    ?)   printf "Использование: %s: [-a] [-b значение] аргументы\n" $0
          exit 2;;
    esac
done
if [ ! -z "$aflag" ]; then
    printf "Опция -a указана\n"
fi
if [ ! -z "$bflag" ]; then
    printf 'Опция -b "%s" указана\n' "$bval"
fi
shift $(($OPTIND - 1))
printf "Оставшиеся аргументы: %s\n" "$*"

Теперь давайте попробуем выполнить данный скрипт:

$ sh a.sh
Оставшиеся аргументы: 
$ sh a.sh -a
Опция -a указана
Оставшиеся аргументы: 
$ sh a.sh -b
Нет аргумента для опции -b
Использование: a.sh: [-a] [-b значение] аргументы
$ sh a.sh -b myval
Опция -b "myval" указана
Оставшиеся аргументы: 
$ sh a.sh -a -b myval
Опция -a указана
Опция -b "myval" указана
Оставшиеся аргументы: 
$ sh a.sh remain
Оставшиеся аргументы: remain
$ sh a.sh -- -a remain
Оставшиеся аргументы: -a remain

Этот код был протестирован в Ubuntu 17.10, где sh является dash версии 0.5.8.

0

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

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

Сохраните файл как 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 be 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
#
# [ и ] обозначают необязательные аргументы
#

# Путь к файлу getopt.sh
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
# Описание опций:
# -------------------
#
# Описание опции основано на встроенной команде getopts в bash. Описание добавляет возможность указывать имя переменной для последующих проверок обязательных или необязательных значений.
# Описание опции добавляет строку "=>VARIABLE_NAME". Имя переменной должно быть ВЕРХНИМ РЕГИСТРОМ. Допустимые символы: [A-Z_]*.
#
# Пример описания опции:
#   OPT_DESC="a:=>A_VARIABLE|b:=>B_VARIABLE|c=>C_VARIABLE"
#
# Опция -a потребует значение (это означает двоеточие) и должна быть сохранена в переменной A_VARIABLE.
# "|" используется для разделения описаний опции.
# Опция -b применяется по тому же принципу, что и -a.
# Опция -c не требует значение (отсутствие двоеточия) и ее наличие должно быть сохранено в C_VARIABLE.
#
#   ~$ echo get_options ${OPT_DESC}
#   a:b:c
#   ~$
#

# Обязательные опции 
REQUIRED_DESC="a:=>REQ_A_VAR_VALUE|B:=>REQ_B_VAR_VALUE|c=>REQ_C_VAR_FLAG"

# Необязательные опции
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
}

function get_options {
    echo ${1} | sed -e "s/\([a-zA-Z]\:\?\)=>[A-Z_]*|\?/\1/g"
}

function get_variables {
    echo ${1} | sed -e "s/[a-zA-Z]\:\?=>\([^|]*\)/\1/g"
}

function get_variable_name {
    VAR=$(echo ${1} | sed -e "s/.*${2}\:\?=>\([^|]*\).*/\1/g")
    if [[ ${VAR} == ${1} ]]; then
        echo ""
    else
        echo ${VAR}
    fi
}

REQUIRED=$(get_options ${REQUIRED_DESC})

OPTIONAL=$(get_options ${OPTIONAL_DESC})

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))

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

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}

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

Чтобы ответить на вопрос, пожалуйста, войдите или зарегистрируйтесь