Вопрос или проблема
Я бы хотел, чтобы работали оба этих ввода. То есть, сам параметр -n
является необязательным – я уже знаю, как это сделать – но затем он может иметь необязательный параметр сверху. Если параметр не указан, будет применено значение по умолчанию.
command -n 100
command -n
Я могу заставить работать только первый тип ввода или последний, но не оба.
HAS_NICE_THINGS=0
NICE_THINGS=50 # значение по умолчанию.
while getopts n: option; do
#while getopts n option; do # NICE_THINGS всегда будет иметь это значение по умолчанию.
#while getopts nn: option; do # то же самое.
case "${option}" in
n)
HAS_NICE_THINGS=1
if [[ ! -z "${OPTARG}" ]] && (( "${OPTARG}" > 0 )) && (( "${OPTARG}" <= 100 )); then
NICE_THINGS=${OPTARG}
fi;;
esac
done
# сообщение об ошибке:
# параметр требует аргумент -- n
Я пока не совсем уверен, нужно ли мне логическое значение для моего скрипта, но на всякий случай я его фиксирую (HAS_NICE_THINGS
).
Конечная цель, которую я имел в виду, заключалась в установке качества JPG при сохранении изображения. Хотя я могу представить, что эта конструкция будет полезна и в других местах.
Я использую Ubuntu 18.04.5
и GNU bash, версия 4.4.20(1)-release (x86_64-pc-linux-gnu)
.
Не разумно использовать getopts
от Bash/POSIX, но вы могли бы сделать это с помощью "усовершенствованного" getopt
(без s) из util-linux или Busybox. (И только с ними, в частности многие "традиционные" реализации getopt
сломаны насчет пробелов.)
В мануале говорится о getopts
:
optstring содержит символы опций, которые нужно распознать; если символ следует за двоеточием, ожидается, что у опции будет аргумент, который должен быть отделен от нее пробелом.
В нем не упоминаются необязательные аргументы опций.
Конечно, вы могли бы иметь другую необязательную опцию, чтобы задать значение не по умолчанию. Например, пусть -n
не принимает аргумент и просто активирует приятные вещи, а -N <arg>
принимает аргумент, включает приятные вещи и устанавливает значение.
Например, что-то вроде этого:
#!/bin/bash
HAS_NICE_THINGS=0
NICE_THINGS_VALUE=50
while getopts nN: option; do
case "${option}" in
n)
HAS_NICE_THINGS=1
shift;;
N)
HAS_NICE_THINGS=1
NICE_THINGS_VALUE=$OPTARG
shift; shift;;
esac
done
if [ "$HAS_NICE_THINGS" = 1 ]; then
echo "приятные вещи включены со значением $NICE_THINGS_VALUE"
fi
это даст
$ bash nice-getotps.sh -n
приятные вещи включены со значением 50
$ bash nice-getopts.sh -N42
приятные вещи включены со значением 42
Утилита getopt
из util-linux принимает необязательные аргументы опций с помощью двойного двоеточия. Это немного неудобно использовать, и вам нужно будет поработать с eval
, но сделав это правильно, кажется, что все работает.
-o shortopts
[...] Каждый короткий символ опции в shortopts может быть последован одним двоеточием, чтобы указать, что у него есть обязательный аргумент, и двумя двоеточиями, чтобы указать, что у него есть необязательный аргумент.
С помощью скрипта, который просто печатает сырые значения, чтобы мы могли проверить, работает ли это правильно (getopt-optional.sh
):
#!/bin/bash
getopt -T
if [ "$?" -ne 4 ]; then
echo "неправильная версия 'getopt', выход..." >&2
exit 1
fi
params="$(getopt -o an:: -- "$@")"
eval set -- "$params"
while [ "$#" -gt 0 ]; do
case "$1" in
-n)
echo "опция -n с аргументом '$2'"
shift 2;;
-a)
echo "опция -a"
shift;;
--)
shift
break;;
*)
echo "что-то другое: '$1'"
shift;;
esac
done
echo "оставшиеся аргументы ($#):"
printf "<%s> " "$@"
echo
мы получим
$ bash getopt-optional.sh -n -a
опция -n с аргументом ''
опция -a
оставшиеся аргументы (0):
<>
$ bash getopt-optional.sh -n'blah blah' -a
-n 'blah blah' -a --
опция -n с аргументом 'blah blah'
опция -a
оставшиеся аргументы (0):
<>
Отсутствие аргумента для -n
отображается как пустой аргумент.
Не думайте, что вы могли бы передать явный пустой аргумент, поскольку аргумент опции должен быть внутри того же аргумента командной строки, что и сама опция, и -n
то же самое, что и -n""
после удаления кавычек. Это делает необязательные аргументы опций неудобными в использовании, потому что вам нужно использовать -nx
, поскольку -n x
будет воспринято как опция -n
(без аргумента опции), за которой следует обычный аргумент командной строки x
. Это отличается от того, что произошло бы, если бы -n
требовал обязательный аргумент опции.
Больше об getopt
в ответе на Stackoverflow на Как разобрать аргументы командной строки в Bash?
Учтите еще раз, что поддержка необязательных аргументов опций ограничена
конкретными реализациями getopt
, которые чаще всего встречаются в системах Linux. Другие реализации getopt
даже не поддерживают пробелы в аргументах, так как полагаются на разбиение слов args=$(getopt...); set -- $args
.
"Усовершенствованные" версии getopt
имеют опцию -T
, чтобы протестировать поддержку этих усовершенствований. Существуют некоторые обсуждения ограничений и особенностей работы с getopt
здесь:
getopt, getopts или ручной разбор - что использовать, когда я хочу поддерживать как короткие, так и длинные опции?
Кроме того, getopt
не является стандартным инструментом, как getopts
(не уверен, связано ли это именно с этими ограничениями и отличиями).
Допустим, мы хотим предоставить опцию -a
, которая принимает необязательный аргумент опции. Использование этой опции должно установить переменную оболочки a_opt
в оболочке, но установить ее в значение $a_default
, если аргумент опции не указан, в противном случае она устанавливается в значение $OPTARG
, как обычно.
Чтобы определить, когда -a
используется без аргумента, у нас есть два случая:
- Аргумент опции, который
getopts
считает, что пользователь использует, на самом деле является одной из других опций, или - Опция
-a
используется в самом конце командной строки (getopts
обычно выдает ошибку о пропущенной опции в этом случае).
Первый случай обрабатывается путем проверки $OPTARG
, чтобы определить, выглядит ли он как одна из других опций. Если да, то используйте значение по умолчанию, иначе используйте $OPTARG
в качестве значения. Когда используется значение по умолчанию, нам нужно перезапустить разбор опций с точки после -a
, что означает сдвиг нескольких аргументов (OPTIND
-1 из них), сброс OPTIND
на 1, а затем добавление $OPTARG
в начало списка позиционных параметров.
Это все означает, что мы не можем использовать -a
с аргументом, который выглядит как одна из опций.
Второй случай обрабатывается тем, что первый символ строки опций, данной getopts
, становится :
. Это имеет два эффекта:
getopts
больше не вызывает ошибок самостоятельно.- Когда опция, требующая аргумент опции, не получает аргумент опции, имя этой опции помещается в
$OPTARG
и знак:
помещается в переменную опции, используемую сgetopts
.
Это означает, что мы можем добавить случай :
в наше обычное выражение case
для разбора опций, которое проверяет $OPTARG
на соответствие a
, чтобы увидеть, использовал ли пользователь -a
в самом конце командной строки.
Код:
#!/usr/local/bin/bash
a_default="Значение по умолчанию"
d_opt=false
while getopts :a:b:c:d opt; do
case $opt in
a)
case $OPTARG in
-[a-d-]*)
shift "$(( OPTIND - 1 ))"
OPTIND=1
set -- "$OPTARG" "$@"
a_opt=$a_default
;;
*)
a_opt=$OPTARG
esac
;;
b) b_opt=$OPTARG ;;
c) c_opt=$OPTARG ;;
d) d_opt=true ;;
:)
if [ "$OPTARG" = a ]; then
a_opt=$a_default
else
printf '%s: опция требует аргумент -- %s\n' \
"$0" "$OPTARG" >&2
exit 1
fi
;;
*) printf '%s: ошибка разбора опций\n' "$0" >&2
exit 1
esac
done
shift "$(( OPTIND - 1 ))"
printf '%s = "%s"\n' \
'a_opt' "${a_opt-(Не установлено)}" \
'b_opt' "${b_opt-(Не установлено)}" \
'c_opt' "${c_opt-(Не установлено)}" \
'd_opt' "${d_opt-(Не установлено)}"
if [ "$#" -gt 0 ]; then
printf 'Дополнительный аргумент: "%s"\n' "$@"
fi
Тестирование:
a_opt
получает значение по умолчанию, если -a
используется без аргумента-опции:
$ ./script -a
a_opt = "Значение по умолчанию"
b_opt = "(Не установлено)"
c_opt = "(Не установлено)"
d_opt = "false"
a_opt
не получает значение, если -a
не используется:
$ ./script -b bval
a_opt = "(Не установлено)"
b_opt = "bval"
c_opt = "(Не установлено)"
d_opt = "false"
Мы можем задать a_opt
значение, как обычно, предоставив аргумент опции с -a
:
$ ./script -da aval -c cval
a_opt = "aval"
b_opt = "(Не установлено)"
c_opt = "cval"
d_opt = "true"
Мы можем все еще использовать --
для окончания разбора опций:
$ ./script -d -a -- aval -c cval hello world
a_opt = "Значение по умолчанию"
b_opt = "(Не установлено)"
c_opt = "(Не установлено)"
d_opt = "true"
Дополнительный аргумент: "aval"
Дополнительный аргумент: "-c"
Дополнительный аргумент: "cval"
Дополнительный аргумент: "hello"
Дополнительный аргумент: "world"
Ответ или решение
Для того чтобы сделать аргументы опции в bash-скрипте необязательными, особенно если вы хотите, чтобы опция могла принимать только значение по умолчанию, можно использовать разные способы. Один из способов — использовать утилиту getopt
, которая поддерживает необязательные аргументы опций, в отличие от встроенной команды getopts
. Рассмотрим реализацию этого метода.
Пример реализации с использованием getopt
-
Проверка поддерживаемой версии
getopt
:
Убедитесь, что ваша версияgetopt
поддерживает необязательные аргументы, выполнив следующую команду:getopt -T
Если команда возвращает код ошибки, это значит, что ваша версия
getopt
не поддерживает нужные функции. -
Основной скрипт:
Создадим bash-скрипт, который принимает опцию-n
, у которой есть необязательный аргумент. Если аргумент не указан, будет использоваться значение по умолчанию.
#!/bin/bash
# Значение по умолчанию
NICE_THINGS_VALUE=50
# Обработка аргументов
params="$(getopt -o n:: -- "$@")"
# Проверка на ошибки
if [ $? -ne 0 ]; then
echo "Ошибка в аргументах. Используйте '-n [значение]'."
exit 1
fi
# Установка параметров
eval set -- "$params"
while true; do
case "$1" in
-n)
if [ -z "$2" ] || [[ "$2" == -* ]]; then
# Если аргумент опции -n отсутствует или является другой опцией
NICE_THINGS_VALUE=50
else
NICE_THINGS_VALUE=$2
shift
fi
shift
;;
--)
shift
break
;;
*)
echo "Неизвестный параметр: $1"
exit 1
;;
esac
done
echo "Значение nice things: $NICE_THINGS_VALUE"
# Остальной код вашего скрипта
Как использовать скрипт
-
Если вызвать скрипт с аргументом:
bash script.sh -n 100
Вывод будет:
Значение nice things: 100
-
Если вызвать скрипт без аргумента для опции
-n
:bash script.sh -n
Вывод будет:
Значение nice things: 50
Таким образом, с помощью команды getopt
, вы можете обрабатывать опции с необязательными аргументами, позволяя вашему скрипту работать гибко с пользовательским вводом.
Заключение
Использование getopt
позволяет создать более сложные сценарии обработки командной строки, чем стандартные возможности getopts
. Если вы ожидаете, что ваши скрипты будут использоваться с различными параметрами, рассмотрите возможность использования более продвинутых инструментов, таких как getopt
, для улучшения взаимодействия с пользователем.