Пример использования getopts в bash
Я хочу вызвать файл 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 ответ(ов)
Ваш скрипт на bash предназначен для обработки аргументов командной строки с использованием getopts
. Давайте разберем, как он работает, и приведем примеры использования.
Функция usage
выводит сообщение о том, как правильно использовать скрипт, и завершает его с кодом ошибки 1.
Цикл while getopts ":s:p:" o; do
позволяет скрипту принимать два параметра:
-s
, который ожидает аргумент, значение которого должно быть либо45
, либо90
.-p
, который также ожидает строковый аргумент.
Если передан недопустимый аргумент, вызывается функция usage
. После обработки параметров с помощью getopts
, скрипт проверяет, заданы ли оба параметра -s
и -p
. Если хотя бы один из них не задан, также вызывается функция usage
.
Далее, если параметры заданы правильно, скрипт выводит их значения.
Примеры использования:
Если запустить скрипт без параметров:
$ ./myscript.sh Usage: ./myscript.sh [-s <45|90>] [-p <string>]
Если вызвать справку с параметром
-h
:$ ./myscript.sh -h Usage: ./myscript.sh [-s <45|90>] [-p <string>]
Если передать пустые значения:
$ ./myscript.sh -s "" -p "" Usage: ./myscript.sh [-s <45|90>] [-p <string>]
Если передать недопустимое значение для параметра
-s
:$ ./myscript.sh -s 10 -p foo Usage: ./myscript.sh [-s <45|90>] [-p <string>]
Если использовать допустимые значения:
$ ./myscript.sh -s 45 -p foo s = 45 p = foo
И еще один пример с другим значением
-s
:$ ./myscript.sh -s 90 -p bar s = 90 p = bar
Этот скрипт является хорошим примером обработки аргументов командной строки в bash и проверок на правильность введенных значений. Если у вас есть дополнительные вопросы, не стесняйтесь их задавать!
Почему использовать getopt
?
Для разбора сложных аргументов командной строки, чтобы избежать путаницы и прояснить передаваемые опции, так что читатель команд сможет понять, что происходит.
Что такое getopt
?
getopt
используется для разбора (парсинга) опций в командных строках, что облегчает их обработку программами оболочки и проверку на допустимые опции. Он использует процедуры GNU getopt(3)
для выполнения этой задачи.
getopt
может иметь следующие типы опций:
- Опции без значений
- Опции в паре ключ-значение
Примечание: В этом документе при объяснении синтаксиса:
- Всякое, что находится внутри
[ ]
, — это необязательный параметр в синтаксисе/примерах. <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
— это то, как опции должны быть указаны в командной строке.
'h'
— это опция без значений.'v:'
говорит о том, что опция-v
имеет значение и является обязательной.:
означает, что имеет значение.'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
Пример, прилагаемый к 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. Обратите внимание на важность кавычек при работе с параметрами, чтобы избежать ошибок при их разборе.
Вот перевод примера из стандарта 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.
Я понимаю, что этот вопрос уже был обсужден, но для записи и для всех, кто столкнулся с аналогичными требованиями, я решил опубликовать этот ответ, связанный с темой. Код изобилует комментариями для пояснения.
Обновленный ответ:
Сохраните файл как 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}
Я не тестировал это тщательно, поэтому могут быть некоторые ошибки.
Как выводить команды оболочки по мере их выполнения
Как указать приватный SSH-ключ для выполнения команды shell в Git?
Расширение переменных внутри одинарных кавычек в команде Bash
Как создать файл в Linux из терминала? [закрыто]
Сравнение null и пустой строки в Bash