Использование getopts для обработки длинных и коротких параметров командной строки

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

Я знаю, что getopts можно использовать, но, как и в Perl, я не смог сделать то же самое с оболочкой.

Любые идеи о том, как это можно сделать, чтобы я мог использовать такие варианты, как:

./shell.sh --copyfile abc.pl /tmp/
./shell.sh -c abc.pl /tmp/

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


person gagneet    schedule 31.12.2008    source источник
comment
ИМХО, принятый ответ не самый лучший. Он не показывает, как использовать getopts для обработки аргументов - и -, что можно сделать, как продемонстрировал @Arvid Requate. Я вставляю еще один ответ, используя аналогичную концепцию, но также имею дело с ошибкой пользователя, когда он забыл вставить значения для необходимых аргументов. Ключевой момент: getopts можно заставить работать. Пользователю следует избегать использования getopt вместо этого, если требуется кроссплатформенная переносимость. Кроме того, getopts является частью стандарта POSIX для оболочек, поэтому он, вероятно, будет переносимым.   -  person pauljohn32    schedule 17.10.2017


Ответы (31)


Можно рассмотреть три реализации:

  • Встроенный Bash getopts. Это не поддерживает длинные имена параметров с префиксом с двойным тире. Он поддерживает только односимвольные варианты.

  • Реализация автономной команды getopt в BSD UNIX (которую использует MacOS). Это также не поддерживает длинные варианты.

  • Реализация GNU автономного getopt. GNU getopt(3) (используется командой -line getopt(1) в Linux) поддерживает синтаксический анализ длинных параметров.


В некоторых других ответах показано решение для использования встроенного bash getopts для имитации длинных параметров. Это решение фактически делает короткий вариант с символом «-». Таким образом, вы получите «-» в качестве флага. Затем все последующее становится OPTARG, и вы тестируете OPTARG с вложенным case.

Это умно, но с оговорками:

  • getopts не может применить спецификацию opt. Он не может возвращать ошибки, если пользователь указал недопустимую опцию. При синтаксическом анализе OPTARG вы должны выполнять свою собственную проверку ошибок.
  • OPTARG используется для длинного имени параметра, что усложняет его использование, когда у самого длинного параметра есть аргумент. В конечном итоге вам придется кодировать это самостоятельно как дополнительный случай.

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

person Bill Karwin    schedule 31.12.2008
comment
Так. Что такое кроссплатформенное портативное решение? - person troelskn; 18.10.2009
comment
GNU Getopt кажется единственным выбором. На Mac установите GNU getopt из macports. В Windows я бы установил GNU getopt с помощью Cygwin. - person Bill Karwin; 18.10.2009
comment
Очевидно, ksh getopts может обрабатывать длинные варианты. - person Tgr; 23.07.2010
comment
@Bill +1, хотя также довольно просто собрать getopt из исходного кода (software.frodo.looijaard. name / getopt) на Mac. Вы также можете проверить версию getopt, установленную в вашей системе, из сценариев с помощью getopt -T; echo $ ?. - person Chinasaur; 30.08.2011
comment
@Bill Karwin: встроенная функция getopts bash не поддерживает длинные имена параметров с префиксом с двойным тире. Но getopts можно настроить для поддержки длинных вариантов: см. stackoverflow.com/a/7680682/915044 ниже. - person TomRoche; 11.08.2014
comment
@troelskn Используйте прекрасный сценарий оболочки, который Стефан Чазелас . Дополнительная информация приведена в моем ответе на связанный пост - person rocky; 12.10.2015
comment
OS X 10.11 и все еще getopt, который поддерживает длинные имена параметров :-( - person tim.rohrer; 26.09.2016
comment
@troelskn - Встроенный getopts, даже в базовых оболочках POSIX, может иметь вложенный case для анализа $OPTARGS параметра -, что позволяет использовать длинные параметры в форме --long (технически -- с аргументом long) и --long=argument (-- с аргумент long=argument), но не с пробелом, разделяющим аргумент, как --long argument. См. мой ответ за одну такую ​​реализацию. - person Adam Katz; 18.01.2019

getopt и getopts - разные звери, и люди, кажется, немного не понимают, что они делают. getopts - это встроенная команда bash для обработки параметров командной строки в цикле и присвоения каждой найденной опции и значения по очереди встроенным переменным, чтобы вы могли в дальнейшем их обрабатывать. getopt, однако, является внешней служебной программой, и на самом деле она не обрабатывает ваши параметры за вас так, как, например, bash getopts, модуль Perl Getopt или модули Python _8 _ / _ 9_. Все, что делает getopt, - это канонизирует переданные параметры, т. Е. Преобразует их в более стандартную форму, чтобы сценарию оболочки было легче их обработать. Например, приложение getopt может преобразовать следующее:

myscript -ab infile.txt -ooutfile.txt

в это:

myscript -a -b -o outfile.txt infile.txt

Вы должны сами выполнить фактическую обработку. Вам совсем не обязательно использовать getopt, если вы накладываете различные ограничения на способ указания опций:

  • только один вариант для каждого аргумента;
  • все параметры идут перед позиционными параметрами (т.е. аргументами, не являющимися параметрами);
  • для параметров со значениями (например, -o выше) значение должно идти как отдельный аргумент (после пробела).

Зачем использовать getopt вместо getopts? Основная причина в том, что только GNU getopt предоставляет вам поддержку параметров командной строки с длинными именами. 1 (GNU getopt используется по умолчанию в Linux. Mac OS X и FreeBSD поставляются с базовым и не очень -полезно getopt, но можно установить версию GNU; см. ниже.)

Например, вот пример использования GNU getopt из моего скрипта javawrap:

# NOTE: This requires GNU getopt.  On Mac OS X and FreeBSD, you have to install this
# separately; see below.
TEMP=`getopt -o vdm: --long verbose,debug,memory:,debugfile:,minheap:,maxheap: \
             -n 'javawrap' -- "$@"`

if [ $? != 0 ] ; then echo "Terminating..." >&2 ; exit 1 ; fi

# Note the quotes around `$TEMP': they are essential!
eval set -- "$TEMP"

VERBOSE=false
DEBUG=false
MEMORY=
DEBUGFILE=
JAVA_MISC_OPT=
while true; do
  case "$1" in
    -v | --verbose ) VERBOSE=true; shift ;;
    -d | --debug ) DEBUG=true; shift ;;
    -m | --memory ) MEMORY="$2"; shift 2 ;;
    --debugfile ) DEBUGFILE="$2"; shift 2 ;;
    --minheap )
      JAVA_MISC_OPT="$JAVA_MISC_OPT -XX:MinHeapFreeRatio=$2"; shift 2 ;;
    --maxheap )
      JAVA_MISC_OPT="$JAVA_MISC_OPT -XX:MaxHeapFreeRatio=$2"; shift 2 ;;
    -- ) shift; break ;;
    * ) break ;;
  esac
done

Это позволяет вам указывать такие параметры, как --verbose -dm4096 --minh=20 --maxhe 40 --debugfi="/Users/John Johnson/debug.txt" или аналогичные. Результатом вызова getopt является канонизация параметров --verbose -d -m 4096 --minheap 20 --maxheap 40 --debugfile "/Users/John Johnson/debug.txt", чтобы вам было легче их обрабатывать. Заключение в кавычки "$1" и "$2" важно, поскольку оно гарантирует правильную обработку аргументов с пробелами.

Если вы удалите первые 9 строк (все до eval set строки), код будет по-прежнему работать! Однако ваш код будет более разборчивым в том, какие типы параметров он принимает: в частности, вам нужно будет указать все параметры в «канонической» форме, описанной выше. Однако с использованием getopt вы можете группировать однобуквенные параметры, использовать более короткие однозначные формы длинных параметров, использовать стиль --file foo.txt или --file=foo.txt, использовать стиль -m 4096 или -m4096, смешивать параметры и не параметры в любой порядок и т. д. getopt также выводит сообщение об ошибке, если обнаруживаются нераспознанные или неоднозначные параметры.

ПРИМЕЧАНИЕ: на самом деле существует две совершенно разные версии getopt, базовая getopt и GNU getopt, с разными функциями и разными соглашениями о вызовах. 2 Basic getopt совершенно не работает: он не только не обрабатывает длинные параметры, он также не может обрабатывать даже встроенные пробелы внутри аргументов или пустые аргументы, тогда как getopts делает это правильно. Приведенный выше код не будет работать в базовой версии getopt. GNU getopt устанавливается по умолчанию в Linux, но в Mac OS X и FreeBSD его нужно устанавливать отдельно. В Mac OS X установите MacPorts (http://www.macports.org), а затем выполните sudo port install getopt, чтобы установить GNU getopt (обычно в /opt/local/bin ) и убедитесь, что /opt/local/bin находится в пути вашей оболочки перед /usr/bin. На FreeBSD установите misc/getopt.

Краткое руководство по изменению примера кода для вашей собственной программы: из первых нескольких строк все является «шаблоном», который должен оставаться неизменным, за исключением строки, которая вызывает getopt. Вы должны изменить имя программы после -n, указать короткие параметры после -o и длинные параметры после --long. Ставьте двоеточие после параметров, принимающих значение.

Наконец, если вы видите код, в котором всего set вместо eval set, он был написан для BSD getopt. Вы должны изменить его, чтобы использовать стиль eval set, который отлично работает с обеими версиями getopt, в то время как простой set не работает правильно с GNU getopt.

1 На самом деле getopts в ksh93 поддерживает параметры с длинными именами, но эта оболочка используется не так часто, как bash. В zsh используйте zparseopts, чтобы получить эту функциональность.

2 Технически «GNU getopt» - неправильное употребление; эта версия была написана для Linux, а не для проекта GNU. Однако он следует всем соглашениям GNU, и обычно используется термин «GNU getopt» (например, во FreeBSD).

person Urban Vagabond    schedule 30.10.2011
comment
Это было очень полезно, идея использования getopt для проверки параметров и последующей обработки этих параметров в очень простом цикле очень хорошо сработала, когда я хотел добавить длинные параметры стиля в сценарий bash. Спасибо. - person ianmjones; 21.12.2011
comment
getopt в Linux не является утилитой GNU, а традиционная getopt изначально пришла не из BSD, а из AT&T Unix. getopts ksh93 (также от AT&T) поддерживает длинные параметры в стиле GNU. - person Stephane Chazelas; 29.01.2013
comment
@StephaneChazelas - отредактировано с учетом ваших комментариев. Я по-прежнему предпочитаю термин GNU getopt, хотя его употребление неверно, потому что эта версия следует соглашениям GNU и в целом действует как программа GNU (например, с использованием POSIXLY_CORRECT), в то время как getopt, расширенный для Linux, ошибочно предполагает, что эта версия существует только в Linux. - person Urban Vagabond; 24.06.2013
comment
Он поступает из пакета util-linux, так что это только Linux, поскольку этот пакет программного обеспечения предназначен только для Linux (это getopt можно легко перенести на другие Unix-системы, но многие другие программы в util-linux специфичны для Linux). Все программы, не относящиеся к GNU, использующие GNU getopt (3), понимают $POSIX_CORRECT. Например, вы бы не сказали, что aplay является GNU только на этом основании. Я подозреваю, что когда FreeBSD упоминает GNU getopt, они имеют в виду GNU getopt (3) C API. - person Stephane Chazelas; 24.06.2013
comment
@StephaneChazelas - FreeBSD выдает сообщение об ошибке Зависимость сборки: пожалуйста, установите GNU getopt, который однозначно ссылается на getopt util, а не на getopt (3). - person Urban Vagabond; 18.10.2013
comment
re getopts является встроенным в bash: кажется, POSIX 2004 добавил его в качестве обязательной утилиты? pubs.opengroup.org/onlinepubs/009695399/utilities/getopts.html - person Calpau; 05.08.2015
comment
Что означает этот фрагмент кода? -n 'javawrap' -- "$@" - person Mohammed Noureldin; 02.12.2016
comment
Спасибо за строку кода if [ $? != 0 ] ; then echo "Terminating..." >&2 ; exit 1 ; fi в примере выше. В противном случае сценарий будет продолжен, даже если будет передан недопустимый параметр командной строки , например. -T для примера выше. Я последовал примеру здесь, но это не завершается недопустимыми параметрами. - person gihanchanuka; 05.07.2017
comment
Отличное объяснение. Но в вашем примере почему бы не поставить shift после esac, тогда вам не нужно будет делать shift для каждого случая. Тогда вам понадобится только shift (не shift 2) для параметров со значениями. - person wisbucky; 16.06.2020
comment
Мне трудно понять, что именно делает eval set. Это безопасно? - person actual_panda; 17.03.2021

Встроенная функция getopts в Bash может использоваться для анализа длинных параметров, помещая в optspec символ дефиса, за которым следует двоеточие:

#!/usr/bin/env bash 
optspec=":hv-:"
while getopts "$optspec" optchar; do
    case "${optchar}" in
        -)
            case "${OPTARG}" in
                loglevel)
                    val="${!OPTIND}"; OPTIND=$(( $OPTIND + 1 ))
                    echo "Parsing option: '--${OPTARG}', value: '${val}'" >&2;
                    ;;
                loglevel=*)
                    val=${OPTARG#*=}
                    opt=${OPTARG%=$val}
                    echo "Parsing option: '--${opt}', value: '${val}'" >&2
                    ;;
                *)
                    if [ "$OPTERR" = 1 ] && [ "${optspec:0:1}" != ":" ]; then
                        echo "Unknown option --${OPTARG}" >&2
                    fi
                    ;;
            esac;;
        h)
            echo "usage: $0 [-v] [--loglevel[=]<value>]" >&2
            exit 2
            ;;
        v)
            echo "Parsing option: '-${optchar}'" >&2
            ;;
        *)
            if [ "$OPTERR" != 1 ] || [ "${optspec:0:1}" = ":" ]; then
                echo "Non-option argument: '-${OPTARG}'" >&2
            fi
            ;;
    esac
done

После копирования в исполняемый файл name = getopts_test.sh в текущем рабочем каталоге можно получить такой вывод, как

$ ./getopts_test.sh
$ ./getopts_test.sh -f
Non-option argument: '-f'
$ ./getopts_test.sh -h
usage: code/getopts_test.sh [-v] [--loglevel[=]<value>]
$ ./getopts_test.sh --help
$ ./getopts_test.sh -v
Parsing option: '-v'
$ ./getopts_test.sh --very-bad
$ ./getopts_test.sh --loglevel
Parsing option: '--loglevel', value: ''
$ ./getopts_test.sh --loglevel 11
Parsing option: '--loglevel', value: '11'
$ ./getopts_test.sh --loglevel=11
Parsing option: '--loglevel', value: '11'

Очевидно, что getopts не выполняет OPTERR проверку или синтаксический анализ аргументов-опций для длинных опций. Приведенный выше фрагмент сценария показывает, как это можно сделать вручную. Основной принцип также работает в оболочке Debian Almquist («тире»). Обратите внимание на особый случай:

getopts -- "-:"  ## without the option terminator "-- " bash complains about "-:"
getopts "-:"     ## this works in the Debian Almquist shell ("dash")

Обратите внимание, что, как указывает GreyCat с http://mywiki.wooledge.org/BashFAQ, это Уловка использует нестандартное поведение оболочки, которое позволяет объединить параметр-аргумент (то есть имя файла в "-f имя_файла") с параметром (как в "-ffilename"). Стандарт POSIX гласит, что между ними должен быть пробел, который в случай "- longoption" завершит синтаксический анализ опций и превратит все longoptions в аргументы, не являющиеся опциями.

person Arvid Requate    schedule 06.10.2011
comment
Один вопрос: какова семантика ! в val="${!OPTIND}? - person TomRoche; 11.08.2014
comment
@TomRoche это косвенная замена: unix.stackexchange.com/a/41293/84316 - person ecbrodie; 16.09.2014
comment
Но у меня есть свой вопрос. Когда анализируется параметр --loglevel long, почему я должен увеличивать значение OPTIND? Разве я не могу просто оставить ОПТИНД как есть? - person ecbrodie; 16.09.2014
comment
@ecbrodie: Это потому, что фактически было обработано два аргумента, а не только один. Первый аргумент - это слово loglevel, а следующий - аргумент этого аргумента. Между тем, getopts автоматически увеличивает только OPTIND на 1, но в нашем случае нам нужно, чтобы он увеличивался на 2, поэтому мы увеличиваем его на 1 вручную, а затем позволяем getopts автоматически увеличивать его на 1 для нас. - person Victor Zamanian; 15.10.2014
comment
Поскольку здесь мы придерживаемся равновесия bash: голые имена переменных разрешены внутри арифметических выражений, $ не требуется. OPTIND=$(( $OPTIND + 1 )) может быть просто OPTIND=$(( OPTIND + 1 )). Еще более интересно то, что вы можете даже назначать и увеличивать переменные внутри арифметического выражения, поэтому можно сокращать его до : $(( ++OPTIND )) или даже (( ++OPTIND )), учитывая, что ++OPTIND всегда будет положительным, так что это не помешает запуску оболочки с вариант -e. :-) gnu.org/software/bash/manual/html_node /Shell-Arithmetic.html - person clacke; 08.04.2016
comment
я получаю сообщение об ошибке «Плохая подстановка» при использовании --loglevel для строки val="${!OPTIND}"; OPTIND=$(( $OPTIND + 1 )) Любая идея, почему? Другое дело, можно ли использовать #!/bin/bash вместо #!/usr/bin/env bash? - person divramod; 10.05.2017
comment
Это должен быть выбран ответ. Мы потратили много времени на попытки других. val = $ {! OPTIND} работает как по волшебству! Спасибо. Единственная проблема, с которой мы сталкиваемся, - это ошибка пользователя. Если они забывают значение аргумента, то в значении появляется имя следующего параметра :) Я работаю над новой версией с контролем / проверкой ошибок. Я использую идеи из BashFaq Q035 mywiki.wooledge.org/BashFAQ/035, как я могу справиться параметры командной строки и аргументы в моем сценарии легко? - person pauljohn32; 17.10.2017
comment
Можно ли вынести эту логику в отдельную функцию? Мне не удалось найти удобный способ проехать - person ITL; 30.10.2017
comment
Почему --very-bad не предупреждает? - person Tom Hale; 02.11.2017
comment
Почему для bash не существует улучшенной функции обработки опций или встроенной функции? Обработка параметров командной строки для bash - важная и широко используемая функция, но и getopt, и getopts имеют серьезные, хорошо известные ограничения, как описано на этой странице. Другие инструменты / языки разработали комплексные, надежные и простые в использовании инструменты обработки интерфейса командной строки. Например, argparse, Click и другие синтаксические анализаторы CLI доступны программистам на Python. Почему никто не добавил эту функциональность в bash? - person Arthur; 02.04.2018
comment
Вот это да. Я пытался сделать то же самое, но был поражен этой красотой и простотой. Ваше решение включает в себя обновление OPTIND, оно позволяет легко увидеть длинные имена параметров в коде и т. Д. На данный момент это лучшее решение для обработки --options на очень старой оболочке, например, мне приходится использовать слишком много раз. - person Olivier Dulac; 12.04.2018
comment
@divramod: предпочтительно использовать #!/usr/bin/env someshell вместо #!/path/to/someshell: env почти гарантированно присутствует в /usr/bin, поэтому независимо от хоста будет найден / usr / bin / env (тогда как someshell может иногда находиться в /usr/bin/someshell или на других хостах быть /bin/someshell или даже в других местах). Если вы можете использовать свой сценарий на разных машинах, используйте #!/usr/bin/env bash для надежного запуска bash. - person Olivier Dulac; 12.04.2018
comment
@ arvid-Requate: одна маленькая ошибка: ваше разделение opt и val в длинном варианте --verbose=value: оно работает для verbose=simplevalue, но для более сложного val (т. е. другого типа длинного варианта) может быть неправильная точка разделения ( * =: last = вместо first, и получение opt также может прерваться в зависимости от того, что содержит val). изменить на: opt="$(printf "%s\n" "${OPTARG}" | cut -d'=' -f1 )" ; val="$(printf "%s\n" "${OPTARG}" | cut -d'=' -f2-)" (немного более многоразовый / переносимый, будет разделяться на первом = и не слишком сильно интерпретирует val) - person Olivier Dulac; 12.04.2018
comment
Однако это не похоже на то, что вы могли бы использовать --loglevel или -l для того же результата, что и в примере. Ответ от Шридхара есть. - person openCivilisation; 30.11.2019
comment
когда я тестировал этот пример, я обнаружил, что с добавлением более одного слова arg (добавьте еще одно после loglevel, скажем, testvar), только первый arg имеет какую-либо функцию. второй аргумент игнорировался в mac os bash. - person openCivilisation; 01.12.2019
comment
@TomHale --very-bad не выдает предупреждения, потому что параметр optspec =: hv-: имеет начало:. Это подавляет предупреждения об отсутствующих или неподдерживаемых параметрах. - person DeeZone; 20.03.2020
comment
Спецификация POSIX гласит: «Если аргумент-опция не предоставляется как отдельный аргумент от символа опции, значение в OPTARG должно быть исключено из символа опции и -», так что такое поведение действительно находится в пределах стандарта. Хотя вы не можете сделать -- loglevel=x, поскольку -- завершит работу getopts, я не вижу обстоятельств, при которых этот синтаксис был бы желателен. Ссылка (в спецификации) на - как неуказанная, поскольку не является буквенно-цифровым, противоречит ссылке в спецификации на «-- аргумент, который не является аргументом-параметром», который определяет поведение для - как символа параметра. - person Adam Katz; 08.06.2020

Встроенная команда getopts по-прежнему, AFAIK, ограничена только односимвольными параметрами.

Существует (или была) внешняя программа getopt, которая реорганизовывала набор параметров таким образом, чтобы его было легче анализировать. Вы также можете адаптировать этот дизайн для работы с длинными вариантами. Пример использования:

aflag=no
bflag=no
flist=""
set -- $(getopt abf: "$@")
while [ $# -gt 0 ]
do
    case "$1" in
    (-a) aflag=yes;;
    (-b) bflag=yes;;
    (-f) flist="$flist $2"; shift;;
    (--) shift; break;;
    (-*) echo "$0: error - unrecognized option $1" 1>&2; exit 1;;
    (*)  break;;
    esac
    shift
done

# Process remaining non-option arguments
...

Вы можете использовать аналогичную схему с командой getoptlong.

Обратите внимание, что фундаментальная слабость внешней getopt программы - это сложность обработки аргументов с пробелами в них и точного сохранения этих пробелов. Вот почему встроенный getopts лучше, хотя и ограничен тем, что обрабатывает только однобуквенные параметры.

person Jonathan Leffler    schedule 31.12.2008
comment
getopt, за исключением версии GNU (которая имеет другое соглашение о вызовах), в корне нарушена. Не используйте это. Пожалуйста, используйте ** getopts вместо bash-hackers.org/wiki/doku.php/ howto / getopts_tutorial - person hendry; 21.08.2009
comment
@hendry - из вашей собственной ссылки: обратите внимание, что getopts не может анализировать длинные параметры в стиле GNU (--myoption) или длинные параметры в стиле XF86 (-myoption)! - person Tom Auger; 18.07.2011
comment
Джонатан - вам следует переписать пример, чтобы использовать eval set с кавычками (см. Мой ответ ниже), чтобы он также правильно работал с GNU getopt (по умолчанию в Linux) и правильно обрабатывал пробелы. - person Urban Vagabond; 22.07.2012
comment
@UrbanVagabond: Не знаю, зачем мне это делать. Вопрос помечен как Unix, а не Linux. Я намеренно показываю традиционный механизм, и у него есть проблемы с пробелами в аргументах и ​​т. Д. Вы можете продемонстрировать современную версию для Linux, если хотите, и ваш ответ сделает это. (Замечу, passim, что ваше использование ${1+"$@"} необычно и противоречит тому, что необходимо в современных оболочках, и особенно с любой оболочкой, которую вы найдете в Linux. См. Использование $ 1: + $ @} в / bin / sh для обсуждения этой нотации.) - person Jonathan Leffler; 22.07.2012
comment
Вы должны это сделать, потому что eval set поступает правильно как с GNU, так и с BSD getopt, тогда как простой set работает правильно только с BSD getopt. Так что вы также можете использовать eval set, чтобы побудить людей привыкнуть к этому. Кстати, спасибо, я не понимал, что ${1+"$@"} больше не нужен. Мне нужно писать вещи, которые работают как в Mac OS X, так и в Linux - между ними двумя они обеспечивают большую переносимость. Я только что проверил, и "$@" действительно работает правильно со всеми sh, bash, ksh и zsh под Mac OS X; обязательно и под линуксом. - person Urban Vagabond; 22.07.2012
comment
Стоит также рассмотреть AIX, Solaris и HP-UX - по крайней мере, я должен учитывать их, а также Linux и Mac OS X. - person Jonathan Leffler; 22.07.2012
comment
Используйте (-f) flist="$2"; shift;;, чтобы перед вашим аргументом flist не появлялся лишний пробел. - person jaketrent; 21.09.2012
comment
@jaketrent: это удаляет предыдущее значение в flist; если опция -f появляется несколько раз, то текущий код сохраняет все значения в flist, разделенные пробелами. Если у вас есть пробелы в именах файлов, о которых нужно беспокоиться, вам придется работать намного усерднее (bash и массивы). - person Jonathan Leffler; 21.09.2012
comment
@Jonathan Leffler: встроенные функции getopts лучше, хотя и ограничены тем, что обрабатывают только однобуквенные параметры. Но getopts можно настроить для поддержки длинных вариантов: см. stackoverflow.com/a/7680682/915044 ниже. - person TomRoche; 11.08.2014
comment
используйте getopt -l, чтобы добавить поддержку длинных параметров, и getopt -a, чтобы разрешить длинные параметры с одним - (это с версией GNU) - person Alexej Magura; 05.06.2017

Длинные параметры могут быть проанализированы стандартной встроенной getopts как «аргументы» для - «опции»

Это переносимая и собственная оболочка POSIX - никаких внешних программ или bashisms не требуется.

В этом руководстве длинные параметры используются в качестве аргументов для параметра -, поэтому --alpha рассматривается getopts как - с аргументом alpha, а --bravo=foo рассматривается как - с аргументом bravo=foo. Истинный аргумент можно получить простой заменой: ${OPTARG#*=}.

В этом примере -b и -c (и их длинные формы --bravo и --charlie) имеют обязательные аргументы. Аргументы в пользу длинных вариантов идут после знаков равенства, например --bravo=foo (разделители пробелов для длинных параметров реализовать будет сложно, см. Ниже).

Поскольку здесь используется _ 17_ встроено, это решение поддерживает использование типа cmd --bravo=foo -ac FILE (которое имеет комбинированные параметры -a и -c и чередует длинные параметры со стандартными параметрами), в то время как большинство других ответов здесь либо борются или не сделать этого.

die() { echo "$*" >&2; exit 2; }  # complain to STDERR and exit with error
needs_arg() { if [ -z "$OPTARG" ]; then die "No arg for --$OPT option"; fi; }

while getopts ab:c:-: OPT; do
  # support long options: https://stackoverflow.com/a/28466267/519360
  if [ "$OPT" = "-" ]; then   # long option: reformulate OPT and OPTARG
    OPT="${OPTARG%%=*}"       # extract long option name
    OPTARG="${OPTARG#$OPT}"   # extract long option argument (may be empty)
    OPTARG="${OPTARG#=}"      # if long option argument, remove assigning `=`
  fi
  case "$OPT" in
    a | alpha )    alpha=true ;;
    b | bravo )    needs_arg; bravo="$OPTARG" ;;
    c | charlie )  needs_arg; charlie="$OPTARG" ;;
    ??* )          die "Illegal option --$OPT" ;;  # bad long option
    ? )            exit 2 ;;  # bad short option (error reported via getopts)
  esac
done
shift $((OPTIND-1)) # remove parsed options and args from $@ list

Если опция представляет собой прочерк (-), это длинный вариант. getopts будет разбирать фактическую длинную опцию на $OPTARG, например --bravo=foo изначально устанавливает OPT='-' и OPTARG='bravo=foo'. Раздел if устанавливает $OPT в содержимое $OPTARG перед первым знаком равенства (bravo в нашем примере), а затем удаляет его из начала $OPTARG (давая =foo на этом шаге, или пустую строку, если = нет). Наконец, мы удаляем ведущий = аргумента. На этом этапе $OPT - это либо короткий вариант (один символ), либо длинный вариант (2+ символа).

case тогда соответствует короткому или длинному варианту. Для коротких параметров getopts автоматически жалуется на параметры и отсутствующие аргументы, поэтому мы должны воспроизвести их вручную с помощью функции needs_arg, которая завершается безвозвратно, когда $OPTARG пусто. Условие ??* будет соответствовать любой оставшейся длинной опции (? соответствует одному символу, а * соответствует нулю или более, поэтому ??* соответствует 2+ символам), что позволяет нам выдать ошибку Illegal option перед выходом.

Незначительная ошибка: если кто-то предоставит недопустимую односимвольную длинную опцию (а это также не короткую опцию), это завершится с ошибкой, но без сообщения. Это потому, что эта реализация предполагает, что это был короткий вариант. Вы можете отследить это с помощью дополнительной переменной в длинном разделе параметров, предшествующем case, а затем проверить это в последнем условии, но я считаю, что это слишком угловой случай, чтобы беспокоиться.

(Примечание об именах переменных, написанных только в верхнем регистре: как правило, рекомендуется зарезервировать переменные только в верхнем регистре для использования в системе. Я сохраняю $OPT как полностью прописные, чтобы соответствовать $OPTARG, но это нарушает это соглашение. I думаю, что это подходит, потому что это то, что система должна была сделать, и это должно быть безопасно, потому что нет стандартов (afaik), которые используют такую ​​переменную.)


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

no_arg() { if [ -n "$OPTARG" ]; then die "No arg allowed for --$OPT option"; fi; }

В более старой версии этого ответа была попытка принять длинные варианты с аргументами, разделенными пробелами, но это было ненадежно; getopts может преждевременно завершиться, если аргумент выходит за пределы его области действия, и ручное увеличение $OPTIND не работает во всех оболочках.

Это может быть выполнено с использованием одного из этих методов в зависимости от вашей оболочки:

а затем заключил что-то вроде [ $# -gt $OPTIND ] && OPTIND=$((OPTIND+1))

person Adam Katz    schedule 11.02.2015
comment
Очень красивое автономное решение. Один вопрос: поскольку letter-c не требует аргументов, будет ли недостаточно использовать letter-c) ?; * кажется лишним. - person Philip Kearns; 14.04.2015
comment
@PhilipKearns был прав, * технически не нужен в letter-c* ), но без него указание аргументов типа --letter-c=oops будет считаться недопустимым, а не неожиданным вариантом для --letter-c. Я добавил строку, чтобы справиться с этим. - person Adam Katz; 14.04.2015
comment
(Чтобы сделать этот ответ более разборчивым, я переименовал вариант --letter-c в --charlie) - person Adam Katz; 16.04.2016
comment
Мне нравится код, но мне не хватает возможности смешивать аргументы и параметры и иметь параметры без =: `command --foo = moo bar --baz waz - person Arne Babenhauserheide; 13.07.2016
comment
вы можете добавить --foo moo, заменив charly * на charly* ) ARG_C="$2" ; shift 1 ;; - person Arne Babenhauserheide; 13.07.2016
comment
@ArneBabenhauserheide - В вашем предыдущем комментарии, что такое bar? Во втором комментарии: --charlie в моем коде не принимает аргумент, но --bravo принимает. В этом случае мы действительно можем использовать shift в тесте для --bravo (без подстановочного знака в конце), но я бы хотел добавить логику, которая отклоняет аргументы, начинающиеся с тире (требуя, чтобы пользователь сделал, например, --bravo=-blah, если это действительно желательно) . Этот тест сделает этот игрушечный пример слишком длинным. - person Adam Katz; 25.08.2016
comment
@Adam bar - позиционный аргумент между параметрами, эквивалентный command --foo=moo --baz waz bar или command bar --foo=moo --baz waz. - person Arne Babenhauserheide; 20.09.2016
comment
@ Адам, почему вы хотите отклонить --bravo -blah? - person Arne Babenhauserheide; 20.09.2016
comment
@Arne Позиционные аргументы - плохой UX; их трудно понять, а необязательные аргументы довольно беспорядочные. getopts останавливается на первом позиционном аргументе, поскольку он не предназначен для их обработки. Это позволяет подкоманды со своими аргументами, например git diff --color, поэтому я бы интерпретировал command --foo=moo bar --baz waz как имеющий --foo как аргумент для command и --baz waz как аргумент (с опцией) для подкоманды bar. Это можно сделать с помощью приведенного выше кода. Я отклоняю --bravo -blah, потому что --bravo требует аргумента, и неясно, что -blah не является другим вариантом. - person Adam Katz; 20.09.2016
comment
Я не согласен с UX: позиционные аргументы полезны и просты, если вы ограничиваете их количество (максимум 2 или 1 плюс N одинаковых типов). Должна быть возможность перемежать их аргументами ключевого слова, потому что затем пользователи могут создавать команду шаг за шагом (например, ls a b c -la). - person Arne Babenhauserheide; 21.09.2016
comment
@Adam: Если ваши аргументы могут быть файлами, отклонение -blah будет сбивать пользователей с толку: с некоторыми файлами все в порядке, с другими - нет. - person Arne Babenhauserheide; 21.09.2016
comment
Хм. @ArneBabenhauserheide: Теперь, когда я пробую это (с тире), ваш дополнительный shift трюк не работает. Похоже, getopts слишком умен и предполагает, что список аргументов завершается до перехода к следующему аргументу; попробуйте bravo ) ARG_B="$2"; shift 1;; с cmd -a --bravo test -c, и вы обнаружите, что он игнорирует параметр -c. - person Adam Katz; 22.04.2017
comment
@AdamKatz да, getopts завершает синтаксический анализ при первом позиционном аргументе. Я забочусь об этом для вызова команды, вызывая ее несколько раз, хотя это не очень хорошо. См. bitbucket.org/ArneBucket.org/ / а> - person Arne Babenhauserheide; 24.04.2017
comment
@AdamKatz: Я написал небольшую статью со следующим: draketo.de/english / free-software / shell-argument-parsing - включает повторное чтение оставшихся аргументов для перехвата конечных параметров. - person Arne Babenhauserheide; 25.04.2017
comment
@ArneBabenhauserheide: Я обновил этот ответ, чтобы поддержать аргументы, разделенные пробелами. Поскольку для этого требуется eval в оболочке POSIX, он указан под остальной частью ответа. - person Adam Katz; 10.04.2019

Вот пример, в котором на самом деле используется getopt с длинными параметрами:

aflag=no
bflag=no
cargument=none

# options may be followed by one colon to indicate they have a required argument
if ! options=$(getopt -o abc: -l along,blong,clong: -- "$@")
then
    # something went wrong, getopt will put out an error message for us
    exit 1
fi

set -- $options

while [ $# -gt 0 ]
do
    case $1 in
    -a|--along) aflag="yes" ;;
    -b|--blong) bflag="yes" ;;
    # for options with required arguments, an additional shift is required
    -c|--clong) cargument="$2" ; shift;;
    (--) shift; break;;
    (-*) echo "$0: error - unrecognized option $1" 1>&2; exit 1;;
    (*) break;;
    esac
    shift
done
person sme    schedule 08.03.2011
comment
Вам следует переписать пример, чтобы использовать eval set с кавычками (см. Мой ответ ниже), чтобы он также правильно работал с GNU getopt (по умолчанию в Linux) и правильно обрабатывал пробелы. - person Urban Vagabond; 22.07.2012
comment
Это использует getopt, хотя вопрос касается getopts. - person Niklas Berglund; 26.08.2016
comment
(--, (-* и (* допустимые шаблоны? Чем они отличаются от --, -* и *? - person Maëlan; 28.03.2019
comment
@ Maëlan - открывающая скобка в начале необязательна, поэтому (--) идентично --) в строфе case. Странно видеть неравномерный отступ и непоследовательное использование этих необязательных начальных скобок, но текущий код ответа мне кажется верным. - person Adam Katz; 28.10.2019

Использование getopts с короткими / длинными параметрами и аргументами


Работает со всеми комбинациями, например:

  • foobar -f --bar
  • foobar --foo -b
  • foobar -bf --bar --foobar
  • foobar -fbFBAshorty --bar -FB --arguments = longhorn
  • foobar -fA "текстовый коротышка" -B --arguments = "текстовый longhorn"
  • bash foobar -F --barfoo
  • sh foobar -B --foobar - ...
  • bash ./foobar -F --bar

Некоторые объявления для этого примера

Options=$@
Optnum=$#
sfoo='no '
sbar='no '
sfoobar='no '
sbarfoo='no '
sarguments='no '
sARG=empty
lfoo='no '
lbar='no '
lfoobar='no '
lbarfoo='no '
larguments='no '
lARG=empty

Как будет выглядеть функция использования

function _usage() 
{
  ###### U S A G E : Help and ERROR ######
  cat <<EOF
   foobar $Options
  $*
          Usage: foobar <[options]>
          Options:
                  -b   --bar            Set bar to yes    ($foo)
                  -f   --foo            Set foo to yes    ($bart)
                  -h   --help           Show this message
                  -A   --arguments=...  Set arguments to yes ($arguments) AND get ARGUMENT ($ARG)
                  -B   --barfoo         Set barfoo to yes ($barfoo)
                  -F   --foobar         Set foobar to yes ($foobar)
EOF
}

[ $# = 0 ] && _usage "  >>>>>>>> no options given "

getops с длинными / короткими флагами, а также с длинными аргументами

while getopts ':bfh-A:BF' OPTION ; do
  case "$OPTION" in
    b  ) sbar=yes                       ;;
    f  ) sfoo=yes                       ;;
    h  ) _usage                         ;;   
    A  ) sarguments=yes;sARG="$OPTARG"  ;;
    B  ) sbarfoo=yes                    ;;
    F  ) sfoobar=yes                    ;;
    -  ) [ $OPTIND -ge 1 ] && optind=$(expr $OPTIND - 1 ) || optind=$OPTIND
         eval OPTION="\$$optind"
         OPTARG=$(echo $OPTION | cut -d'=' -f2)
         OPTION=$(echo $OPTION | cut -d'=' -f1)
         case $OPTION in
             --foo       ) lfoo=yes                       ;;
             --bar       ) lbar=yes                       ;;
             --foobar    ) lfoobar=yes                    ;;
             --barfoo    ) lbarfoo=yes                    ;;
             --help      ) _usage                         ;;
             --arguments ) larguments=yes;lARG="$OPTARG"  ;; 
             * )  _usage " Long: >>>>>>>> invalid options (long) " ;;
         esac
       OPTIND=1
       shift
      ;;
    ? )  _usage "Short: >>>>>>>> invalid options (short) "  ;;
  esac
done

Выход

##################################################################
echo "----------------------------------------------------------"
echo "RESULT short-foo      : $sfoo                                    long-foo      : $lfoo"
echo "RESULT short-bar      : $sbar                                    long-bar      : $lbar"
echo "RESULT short-foobar   : $sfoobar                                 long-foobar   : $lfoobar"
echo "RESULT short-barfoo   : $sbarfoo                                 long-barfoo   : $lbarfoo"
echo "RESULT short-arguments: $sarguments  with Argument = \"$sARG\"   long-arguments: $larguments and $lARG"

Объединение вышеперечисленного в единый сценарий

#!/bin/bash
# foobar: getopts with short and long options AND arguments

function _cleanup ()
{
  unset -f _usage _cleanup ; return 0
}

## Clear out nested functions on exit
trap _cleanup INT EXIT RETURN

###### some declarations for this example ######
Options=$@
Optnum=$#
sfoo='no '
sbar='no '
sfoobar='no '
sbarfoo='no '
sarguments='no '
sARG=empty
lfoo='no '
lbar='no '
lfoobar='no '
lbarfoo='no '
larguments='no '
lARG=empty

function _usage() 
{
  ###### U S A G E : Help and ERROR ######
  cat <<EOF
   foobar $Options
   $*
          Usage: foobar <[options]>
          Options:
                  -b   --bar            Set bar to yes    ($foo)
                    -f   --foo            Set foo to yes    ($bart)
                      -h   --help           Show this message
                  -A   --arguments=...  Set arguments to yes ($arguments) AND get ARGUMENT ($ARG)
                  -B   --barfoo         Set barfoo to yes ($barfoo)
                  -F   --foobar         Set foobar to yes ($foobar)
EOF
}

[ $# = 0 ] && _usage "  >>>>>>>> no options given "

##################################################################    
#######  "getopts" with: short options  AND  long options  #######
#######            AND  short/long arguments               #######
while getopts ':bfh-A:BF' OPTION ; do
  case "$OPTION" in
    b  ) sbar=yes                       ;;
    f  ) sfoo=yes                       ;;
    h  ) _usage                         ;;   
    A  ) sarguments=yes;sARG="$OPTARG"  ;;
    B  ) sbarfoo=yes                    ;;
    F  ) sfoobar=yes                    ;;
    -  ) [ $OPTIND -ge 1 ] && optind=$(expr $OPTIND - 1 ) || optind=$OPTIND
         eval OPTION="\$$optind"
         OPTARG=$(echo $OPTION | cut -d'=' -f2)
         OPTION=$(echo $OPTION | cut -d'=' -f1)
         case $OPTION in
             --foo       ) lfoo=yes                       ;;
             --bar       ) lbar=yes                       ;;
             --foobar    ) lfoobar=yes                    ;;
             --barfoo    ) lbarfoo=yes                    ;;
             --help      ) _usage                         ;;
               --arguments ) larguments=yes;lARG="$OPTARG"  ;; 
             * )  _usage " Long: >>>>>>>> invalid options (long) " ;;
         esac
       OPTIND=1
       shift
      ;;
    ? )  _usage "Short: >>>>>>>> invalid options (short) "  ;;
  esac
done
person RapaNui    schedule 21.09.2012
comment
Разве это не работает с более чем одним длинным аргументом (-). Кажется, я прочитал только первую. - person Sinaesthetic; 13.12.2018
comment
@Sinaesthetic - Да, я играл с подходом eval для разнесенных аргументов в длинных параметрах и нашел его ненадежным с некоторыми оболочками (хотя я ожидаю, что он будет работать с bash, и в этом случае вам не нужно использовать eval). См. мой ответ, чтобы узнать, как принимать длинные аргументы параметра с = и мои отмеченные попытки использования пространства. Мое решение не выполняет внешних вызовов, в то время как это несколько раз использует cut. - person Adam Katz; 10.01.2020

Взгляните на shFlags, которая представляет собой переносимую библиотеку оболочки (что означает: sh, bash, dash, ksh, zsh в Linux, Solaris и т. д.).

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

Вот простой Hello, world!, использующий shFlag:

#!/bin/sh

# source shflags from current directory
. ./shflags

# define a 'name' command-line string flag
DEFINE_string 'name' 'world' 'name to say hello to' 'n'

# parse the command-line
FLAGS "$@" || exit 1
eval set -- "${FLAGS_ARGV}"

# say hello
echo "Hello, ${FLAGS_name}!"

Для ОС с расширенным getopt, поддерживающим длинные параметры (например, Linux), вы можете:

$ ./hello_world.sh --name Kate
Hello, Kate!

В остальном необходимо использовать короткий вариант:

$ ./hello_world.sh -n Kate
Hello, Kate!

Добавить новый флаг так же просто, как добавить новый DEFINE_ call.

person Community    schedule 15.05.2009
comment
Это фантастика, но, к сожалению, мой getopt (OS X) не поддерживает пробелы в аргументах: / интересно, есть ли альтернатива. - person Alastair Stuart; 16.03.2011
comment
@AlastairStuart - в OS X действительно есть альтернатива. Используйте MacPorts для установки GNU getopt (обычно он устанавливается в / opt / local / bin / getopt). - person Urban Vagabond; 09.07.2012
comment
@UrbanVagabond - установка не системных инструментов по умолчанию, к сожалению, не является приемлемым требованием для достаточно портативного инструмента. - person Alastair Stuart; 09.07.2012
comment
@AlastairStuart - см. мой ответ о портативном решении, в котором используется встроенная функция getopts, а не GNU getopt. Это то же самое, что и базовое использование getopts, но с дополнительной итерацией для длинных опций. - person Adam Katz; 31.08.2017

Другой путь...

# translate long options to short
for arg
do
    delim=""
    case "$arg" in
       --help) args="${args}-h ";;
       --verbose) args="${args}-v ";;
       --config) args="${args}-c ";;
       # pass through anything else
       *) [[ "${arg:0:1}" == "-" ]] || delim="\""
           args="${args}${delim}${arg}${delim} ";;
    esac
done
# reset the translated args
eval set -- $args
# now we can process with getopt
while getopts ":hvc:" opt; do
    case $opt in
        h)  usage ;;
        v)  VERBOSE=true ;;
        c)  source $OPTARG ;;
        \?) usage ;;
        :)
        echo "option -$OPTARG requires an argument"
        usage
        ;;
    esac
done
person mtvee    schedule 10.03.2011
comment
Разве для этого не нужен пробел при каждом $args переназначении? Это можно было бы сделать даже без лишних слов, но этот код потеряет пробелы в параметрах и аргументах (я не думаю, что трюк $delim сработает). Вместо этого вы можете запустить set внутри цикла for, если вы достаточно осторожны, чтобы очистить его только на первой итерации. Вот более безопасная версия без башизмов. - person Adam Katz; 16.04.2016

Я как бы решил так:

# A string with command options
options=$@

# An array with all the arguments
arguments=($options)

# Loop index
index=0

for argument in $options
  do
    # Incrementing index
    index=`expr $index + 1`

    # The conditions
    case $argument in
      -a) echo "key $argument value ${arguments[index]}" ;;
      -abc) echo "key $argument value ${arguments[index]}" ;;
    esac
  done

exit;

Я тупой что ли? getopt и getopts так сбивают с толку.

person Community    schedule 04.08.2011
comment
Мне кажется, это работает, я не знаю, в чем проблема с этим методом, но он кажется простым, поэтому должна быть причина, по которой все остальные не используют его. - person Billy Moon; 07.08.2011
comment
@Billy Да, это просто, потому что я не использую никаких скриптов для управления своими параметрами и т. Д. Обычно я конвертирую строку аргументов ($ @) в массив и просматриваю ее в цикле. В цикле текущее значение будет ключевым, а следующее будет значением. Просто как тот. - person ; 24.08.2011
comment
@ Теодор, я рад, что это помогло тебе! Для меня это тоже было больно. Если вам интересно, вы можете увидеть пример этого в действии здесь: raw.github.com/rafaelrinaldi/swf-to-html/master/swf-to-html.sh - person ; 24.08.2011
comment
Простой. Он кладет широкие мазки. Большое спасибо за ваш комментарий ... Я обнаружил, что вы можете использовать --option) или --option = *) в каждом случае для обработки обоих типов присваивания (пробелы или равные). В последнем случае просто обработайте $ аргумент с помощью: value = $ {argument / * = /} вместо: $ {arguments [index]}, чтобы получить свое значение. - person Shaun; 13.09.2012
comment
Определенно самый простой способ, который я видел. Я немного изменил его, например, использовал i = $ (($ i + 1)) вместо expr, но концепция герметична. - person Thomas Dignan; 14.11.2012
comment
Вы совсем не тупица, но вам может не хватать одной функции: getopt (s) может распознавать смешанные параметры (например: -ltr или -lt -r, а также -l -t -r). Кроме того, он обеспечивает некоторую обработку ошибок и простой способ отменить обрабатываемые параметры после завершения обработки параметров. - person Olivier Dulac; 28.11.2012
comment
Несмотря на недостатки, описанные @ olivier-dulac, на мой взгляд, это лучший ответ. Безусловно, самый простой в реализации. Можно также объявить ассоциативный массив aa declare -A aa, заполнить его aa[$argument]=${arguments[index]}, а затем получить эти аргументы с помощью ${aa["-a"]}. - person tommy.carstensen; 27.01.2015

Если вам не нужна зависимость getopt, вы можете сделать это:

while test $# -gt 0
do
  case $1 in

  # Normal option processing
    -h | --help)
      # usage and help
      ;;
    -v | --version)
      # version info
      ;;
  # ...

  # Special cases
    --)
      break
      ;;
    --*)
      # error unknown (long) option $1
      ;;
    -?)
      # error unknown (short) option $1
      ;;

  # FUN STUFF HERE:
  # Split apart combined short options
    -*)
      split=$1
      shift
      set -- $(echo "$split" | cut -c 2- | sed 's/./-& /g') "$@"
      continue
      ;;

  # Done with options
    *)
      break
      ;;
  esac

  # for testing purposes:
  echo "$1"

  shift
done

Конечно, тогда нельзя использовать длинные варианты стиля с одним тире. И если вы хотите добавить сокращенные версии (например, --verbos вместо --verbose), вам нужно добавить их вручную.

Но если вы хотите получить getopts функциональность вместе с длинными параметрами, это простой способ сделать это.

Я также помещаю этот фрагмент в суть.

person jakesandlund    schedule 27.03.2012
comment
Кажется, это работает только с одним длинным вариантом за раз, но это удовлетворило мою потребность. Спасибо! - person kingjeffrey; 21.01.2013
comment
В особом случае --) кажется, что отсутствует shift ;. На данный момент -- останется первым аргументом без опции. - person dgw; 31.01.2014
comment
Я думаю, что это на самом деле лучший ответ, хотя, как указывает dgw, опция -- требует наличия shift. Я говорю, что это лучше, потому что альтернативы являются либо зависящими от платформы версиями getopt или getopts_long, либо вам нужно принудительно использовать короткие параметры только в начале команды (т. Е. Вы используете getopts, а затем обрабатываете длинные параметры), тогда как это дает любой порядок и полный контроль. - person Haravikk; 19.02.2014
comment
Этот ответ заставляет меня задаться вопросом, почему у нас есть цепочка из десятков ответов для выполнения работы, которую нельзя выполнить с помощью ничего, кроме этого абсолютно ясного и простого решения, и есть ли какая-то причина для миллиарда getopt ( s) варианты использования, кроме самоутверждения. - person Florian Heigl; 07.08.2017

Встроенный getopts не может этого сделать. Существует внешняя программа getopt (1), которая может это сделать, но вы получаете ее в Linux только из пакета util-linux. Он поставляется с примером сценария getopt-parse.bash.

Также существует getopts_long, написанная как функция оболочки.

person Nietzche-jou    schedule 31.12.2008
comment
getopt был включен в FreeBSD версии 1.0 в 1993 году и с тех пор является частью FreeBSD. Таким образом, он был заимствован из FreeBSD 4.x для включения в проект Apple Darwin. Начиная с OS X 10.6.8, страница руководства, включенная Apple, остается точной копией страницы руководства FreeBSD. Так что да, он включен в OS X и многие другие операционные системы помимо Linux. -1 на этот ответ за дезинформацию. - person ghoti; 27.03.2012

В ksh93 getopts действительно поддерживает длинные имена ...

while getopts "f(file):s(server):" flag
do
    echo "$flag" $OPTIND $OPTARG
done

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

person Richard Lynch    schedule 07.01.2011
comment
Это встроенный getopts ksh93. Помимо этого синтаксиса, он также имеет более сложный синтаксис, который также позволяет использовать длинные параметры без короткого эквивалента и многое другое. - person jilles; 09.10.2011
comment
Разумный ответ. OP не указал, ЧТО оболочка. - person ghoti; 27.03.2012

Изобретая еще одну версию колеса ...

Эта функция является (надеюсь) POSIX-совместимой простой заменой оболочки GNU getopt. Он поддерживает короткие / длинные параметры, которые могут принимать обязательные / необязательные / без аргументов, а способ указания параметров почти идентичен GNU getopt, поэтому преобразование тривиально.

Конечно, это по-прежнему значительный кусок кода, который нужно вставить в сценарий, но он примерно в половине строк хорошо известной функции оболочки getopt_long, и может быть предпочтительнее в тех случаях, когда вы просто хотите заменить существующие GNU использования getopt.

Это довольно новый код, так что YMMV (и обязательно дайте мне знать, если он по какой-либо причине на самом деле не совместим с POSIX - переносимость была намерением с самого начала, но у меня нет полезной тестовой среды POSIX).

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

#!/bin/sh
# posix_getopt shell function
# Author: Phil S.
# Version: 1.0
# Created: 2016-07-05
# URL: http://stackoverflow.com/a/37087374/324105

# POSIX-compatible argument quoting and parameter save/restore
# http://www.etalabs.net/sh_tricks.html
# Usage:
# parameters=$(save "$@") # save the original parameters.
# eval "set -- ${parameters}" # restore the saved parameters.
save () {
    local param
    for param; do
        printf %s\\n "$param" \
            | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/"
    done
    printf %s\\n " "
}

# Exit with status $1 after displaying error message $2.
exiterr () {
    printf %s\\n "$2" >&2
    exit $1
}

# POSIX-compatible command line option parsing.
# This function supports long options and optional arguments, and is
# a (largely-compatible) drop-in replacement for GNU getopt.
#
# Instead of:
# opts=$(getopt -o "$shortopts" -l "$longopts" -- "$@")
# eval set -- ${opts}
#
# We instead use:
# opts=$(posix_getopt "$shortopts" "$longopts" "$@")
# eval "set -- ${opts}"
posix_getopt () { # args: "$shortopts" "$longopts" "$@"
    local shortopts longopts \
          arg argtype getopt nonopt opt optchar optword suffix

    shortopts="$1"
    longopts="$2"
    shift 2

    getopt=
    nonopt=
    while [ $# -gt 0 ]; do
        opt=
        arg=
        argtype=
        case "$1" in
            # '--' means don't parse the remaining options
            ( -- ) {
                getopt="${getopt}$(save "$@")"
                shift $#
                break
            };;
            # process short option
            ( -[!-]* ) {         # -x[foo]
                suffix=${1#-?}   # foo
                opt=${1%$suffix} # -x
                optchar=${opt#-} # x
                case "${shortopts}" in
                    ( *${optchar}::* ) { # optional argument
                        argtype=optional
                        arg="${suffix}"
                        shift
                    };;
                    ( *${optchar}:* ) { # required argument
                        argtype=required
                        if [ -n "${suffix}" ]; then
                            arg="${suffix}"
                            shift
                        else
                            case "$2" in
                                ( -* ) exiterr 1 "$1 requires an argument";;
                                ( ?* ) arg="$2"; shift 2;;
                                (  * ) exiterr 1 "$1 requires an argument";;
                            esac
                        fi
                    };;
                    ( *${optchar}* ) { # no argument
                        argtype=none
                        arg=
                        shift
                        # Handle multiple no-argument parameters combined as
                        # -xyz instead of -x -y -z. If we have just shifted
                        # parameter -xyz, we now replace it with -yz (which
                        # will be processed in the next iteration).
                        if [ -n "${suffix}" ]; then
                            eval "set -- $(save "-${suffix}")$(save "$@")"
                        fi
                    };;
                    ( * ) exiterr 1 "Unknown option $1";;
                esac
            };;
            # process long option
            ( --?* ) {            # --xarg[=foo]
                suffix=${1#*=}    # foo (unless there was no =)
                if [ "${suffix}" = "$1" ]; then
                    suffix=
                fi
                opt=${1%=$suffix} # --xarg
                optword=${opt#--} # xarg
                case ",${longopts}," in
                    ( *,${optword}::,* ) { # optional argument
                        argtype=optional
                        arg="${suffix}"
                        shift
                    };;
                    ( *,${optword}:,* ) { # required argument
                        argtype=required
                        if [ -n "${suffix}" ]; then
                            arg="${suffix}"
                            shift
                        else
                            case "$2" in
                                ( -* ) exiterr 1 \
                                       "--${optword} requires an argument";;
                                ( ?* ) arg="$2"; shift 2;;
                                (  * ) exiterr 1 \
                                       "--${optword} requires an argument";;
                            esac
                        fi
                    };;
                    ( *,${optword},* ) { # no argument
                        if [ -n "${suffix}" ]; then
                            exiterr 1 "--${optword} does not take an argument"
                        fi
                        argtype=none
                        arg=
                        shift
                    };;
                    ( * ) exiterr 1 "Unknown option $1";;
                esac
            };;
            # any other parameters starting with -
            ( -* ) exiterr 1 "Unknown option $1";;
            # remember non-option parameters
            ( * ) nonopt="${nonopt}$(save "$1")"; shift;;
        esac

        if [ -n "${opt}" ]; then
            getopt="${getopt}$(save "$opt")"
            case "${argtype}" in
                ( optional|required ) {
                    getopt="${getopt}$(save "$arg")"
                };;
            esac
        fi
    done

    # Generate function output, suitable for:
    # eval "set -- $(posix_getopt ...)"
    printf %s "${getopt}"
    if [ -n "${nonopt}" ]; then
        printf %s "$(save "--")${nonopt}"
    fi
}

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

# Process command line options
shortopts="hvd:c::s::L:D"
longopts="help,version,directory:,client::,server::,load:,delete"
#opts=$(getopt -o "$shortopts" -l "$longopts" -n "$(basename $0)" -- "$@")
opts=$(posix_getopt "$shortopts" "$longopts" "$@")
if [ $? -eq 0 ]; then
    #eval set -- ${opts}
    eval "set -- ${opts}"
    while [ $# -gt 0 ]; do
        case "$1" in
            ( --                ) shift; break;;
            ( -h|--help         ) help=1; shift; break;;
            ( -v|--version      ) version_help=1; shift; break;;
            ( -d|--directory    ) dir=$2; shift 2;;
            ( -c|--client       ) useclient=1; client=$2; shift 2;;
            ( -s|--server       ) startserver=1; server_name=$2; shift 2;;
            ( -L|--load         ) load=$2; shift 2;;
            ( -D|--delete       ) delete=1; shift;;
        esac
    done
else
    shorthelp=1 # getopt returned (and reported) an error.
fi
person phils    schedule 07.05.2016
comment
Мне трудно понять, что именно делает eval set. Это безопасно? - person actual_panda; 17.03.2021
comment
Это безопасно, если аргументы (следующие за всеми обычными процессами замены и расширения оболочки) производят безопасное выражение. В этом случае мы создаем команду set -- ..., которая объясняется на странице руководства для тире примерно так: Третье использование команды set - установить значения позиционных параметров оболочки на указанные аргументы. Чтобы изменить позиционные параметры без изменения каких-либо опций, используйте «-» в качестве первого аргумента для установки. (Документы оболочки POSIX говорят то же самое, но менее четко.) - person phils; 17.03.2021
comment
Спасибо. Я еще не полностью понял две вещи. 1. Зачем нужен eval? Мои исследования показывают только то, что вам это нужно при использовании GNU getopt. Но почему? 2. Можем ли мы предположить, что аргументы производят безопасное выражение? - person actual_panda; 18.03.2021
comment
eval необходим, потому что мы оба (GNU getopt и posix_getopt) поддерживаем параметры, содержащие специальные символы, на основании генерации строки значений, заключенных в кавычки, которые после evaluated снова производят исходные значения (теперь манипулируют в нормализованный формат, готовый к обработке). Например, в противном случае пробел в --foo="hello, world." был бы проблемой. Возвращаемое значение posix_getopt должно быть передано через eval способом, продемонстрированным для работы. - person phils; 18.03.2021
comment
Что касается (2), все, что я могу сказать, это то, что я уверен, что создается безопасное выражение (в конце концов, я написал это для себя), но я не уверен, что смогу сделать более чем поощрять вас читать / понимать код, чтобы убедиться в том же. Чтобы понять, что происходит, вы можете попробовать передать ему различные небезопасные входные данные, а затем вместо eval "set -- ${opts}" вместо echo "set -- ${opts}", чтобы увидеть, что будет оценено. - person phils; 18.03.2021
comment
См. Также заголовок «Работа с массивами» на странице etalabs.net/sh_tricks.html, в котором объясняется техника в вопрос поподробнее. - person phils; 18.03.2021

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

Используя стратегию, предложенную @Arvid Requate, мы заметили некоторые пользовательские ошибки. Пользователь, который забыл включить значение, случайно получит имя следующей опции, обработанное как значение:

./getopts_test.sh --loglevel= --toc=TRUE

приведет к тому, что значение "loglevel" будет отображаться как "--toc = TRUE". Этого можно избежать.

Я адаптировал некоторые идеи о проверке ошибок пользователя для CLI из http://mwiki.wooledge.org/BashFAQ/035 обсуждение ручного парсинга. Я вставил проверку ошибок в обработку аргументов «-» и «-».

Затем я начал возиться с синтаксисом, так что любые ошибки здесь строго моя вина, а не первоначальных авторов.

Мой подход помогает пользователям, которые предпочитают длинные позиции со знаком равенства или без него. То есть он должен иметь такой же ответ на «--loglevel 9», что и «--loglevel = 9». В методе - / space невозможно точно узнать, забыл ли пользователь аргумент, поэтому необходимо некоторое предположение.

  1. если у пользователя формат длинного знака / знака равенства (--opt =), то пробел после = вызывает ошибку, поскольку аргумент не был предоставлен.
  2. если у пользователя есть длинные / пробельные аргументы (--opt), этот сценарий вызывает сбой, если не следует аргумент (конец команды) или если аргумент начинается с тире)

Если вы только начинаете, есть интересная разница между форматами «--opt = value» и «--opt value». Со знаком равенства аргумент командной строки отображается как «opt = value», а работа, которую нужно обработать, - это синтаксический анализ строки, разделяется знаком «=». Напротив, с «--opt value» имя аргумента - «opt», и у нас есть проблема получить следующее значение, указанное в командной строке. Здесь @Arvid Requate использовал косвенную ссылку $ {! OPTIND}. Я до сих пор этого не понимаю, ну, и комментарии в BashFAQ, кажется, предостерегают от этого стиля (http://mywiki.wooledge.org/BashFAQ/006). Кстати, я не думаю, что предыдущие комментарии автора о важности OPTIND = $ (($ OPTIND + 1)) верны. Я хочу сказать, что не вижу вреда в том, чтобы его пропустить.

В последней версии этого скрипта флаг -v означает распечатку VERBOSE.

Сохраните его в файл с именем "cli-5.sh", сделайте исполняемым, и любой из них будет работать или отказывать желаемым образом.

./cli-5.sh  -v --loglevel=44 --toc  TRUE
./cli-5.sh  -v --loglevel=44 --toc=TRUE
./cli-5.sh --loglevel 7
./cli-5.sh --loglevel=8
./cli-5.sh -l9

./cli-5.sh  --toc FALSE --loglevel=77
./cli-5.sh  --toc=FALSE --loglevel=77

./cli-5.sh   -l99 -t yyy
./cli-5.sh   -l 99 -t yyy

Вот пример вывода проверки ошибок на пользовательском intpu

$ ./cli-5.sh  --toc --loglevel=77
ERROR: toc value must not have dash at beginning
$ ./cli-5.sh  --toc= --loglevel=77
ERROR: value for toc undefined

Вам следует подумать о включении -v, потому что он распечатывает внутреннюю часть OPTIND и OPTARG

#/usr/bin/env bash

## Paul Johnson
## 20171016
##

## Combines ideas from
## https://stackoverflow.com/questions/402377/using-getopts-in-bash-shell-script-to-get-long-and-short-command-line-options
## by @Arvid Requate, and http://mwiki.wooledge.org/BashFAQ/035

# What I don't understand yet: 
# In @Arvid REquate's answer, we have 
# val="${!OPTIND}"; OPTIND=$(( $OPTIND + 1 ))
# this works, but I don't understand it!


die() {
    printf '%s\n' "$1" >&2
    exit 1
}

printparse(){
    if [ ${VERBOSE} -gt 0 ]; then
        printf 'Parse: %s%s%s\n' "$1" "$2" "$3" >&2;
    fi
}

showme(){
    if [ ${VERBOSE} -gt 0 ]; then
        printf 'VERBOSE: %s\n' "$1" >&2;
    fi
}


VERBOSE=0
loglevel=0
toc="TRUE"

optspec=":vhl:t:-:"
while getopts "$optspec" OPTCHAR; do

    showme "OPTARG:  ${OPTARG[*]}"
    showme "OPTIND:  ${OPTIND[*]}"
    case "${OPTCHAR}" in
        -)
            case "${OPTARG}" in
                loglevel) #argument has no equal sign
                    opt=${OPTARG}
                    val="${!OPTIND}"
                    ## check value. If negative, assume user forgot value
                    showme "OPTIND is {$OPTIND} {!OPTIND} has value \"${!OPTIND}\""
                    if [[ "$val" == -* ]]; then
                        die "ERROR: $opt value must not have dash at beginning"
                    fi
                    ## OPTIND=$(( $OPTIND + 1 )) # CAUTION! no effect?
                    printparse "--${OPTARG}" "  " "${val}"
                    loglevel="${val}"
                    shift
                    ;;
                loglevel=*) #argument has equal sign
                    opt=${OPTARG%=*}
                    val=${OPTARG#*=}
                    if [ "${OPTARG#*=}" ]; then
                        printparse "--${opt}" "=" "${val}"
                        loglevel="${val}"
                        ## shift CAUTION don't shift this, fails othewise
                    else
                        die "ERROR: $opt value must be supplied"
                    fi
                    ;;
                toc) #argument has no equal sign
                    opt=${OPTARG}
                    val="${!OPTIND}"
                    ## check value. If negative, assume user forgot value
                    showme "OPTIND is {$OPTIND} {!OPTIND} has value \"${!OPTIND}\""
                    if [[ "$val" == -* ]]; then
                        die "ERROR: $opt value must not have dash at beginning"
                    fi
                    ## OPTIND=$(( $OPTIND + 1 )) #??
                    printparse "--${opt}" " " "${val}"
                    toc="${val}"
                    shift
                    ;;
                toc=*) #argument has equal sign
                    opt=${OPTARG%=*}
                    val=${OPTARG#*=}
                    if [ "${OPTARG#*=}" ]; then
                        toc=${val}
                        printparse "--$opt" " -> " "$toc"
                        ##shift ## NO! dont shift this
                    else
                        die "ERROR: value for $opt undefined"
                    fi
                    ;;

                help)
                    echo "usage: $0 [-v] [--loglevel[=]<value>] [--toc[=]<TRUE,FALSE>]" >&2
                    exit 2
                    ;;
                *)
                    if [ "$OPTERR" = 1 ] && [ "${optspec:0:1}" != ":" ]; then
                        echo "Unknown option --${OPTARG}" >&2
                    fi
                    ;;
            esac;;
        h|-\?|--help)
            ## must rewrite this for all of the arguments
            echo "usage: $0 [-v] [--loglevel[=]<value>] [--toc[=]<TRUE,FALSE>]" >&2
            exit 2
            ;;
        l)
            loglevel=${OPTARG}
            printparse "-l" " "  "${loglevel}"
            ;;
        t)
            toc=${OPTARG}
            ;;
        v)
            VERBOSE=1
            ;;

        *)
            if [ "$OPTERR" != 1 ] || [ "${optspec:0:1}" = ":" ]; then
                echo "Non-option argument: '-${OPTARG}'" >&2
            fi
            ;;
    esac
done



echo "
After Parsing values
"
echo "loglevel  $loglevel" 
echo "toc  $toc"
person pauljohn32    schedule 17.10.2017
comment
OPTIND=$(( $OPTIND + 1 )): он необходим всякий раз, когда вы "проглатываете" параметр OPTIND (например: когда использованное --toc value: значение находится в параметре номер $ OPTIND. После того, как вы получите его для значения toc, вы должны сообщить getopts, что следующий параметр для синтаксического анализа не значение, но одно после него (следовательно: OPTIND=$(( $OPTIND + 1 )) . и ваш скрипт (а также скрипт, на который вы ссылаетесь) отсутствуют после done: shift $(( $OPTIND -1 )) (поскольку getopts завершились после синтаксического анализа параметров 1 в OPTIND-1, вам необходимо сдвиньте их так, чтобы $@ теперь оставались параметрами, не являющимися опциями - person Olivier Dulac; 13.04.2018
comment
о, когда вы меняете себя, вы меняете параметры под getopts, поэтому OPTIND всегда указывает правильную вещь ... но я нахожу это очень запутанным. Я считаю (прямо сейчас не могу проверить ваш скрипт), что вам все еще нужен сдвиг $ (($ OPTIND - 1)) после цикла getopts while, так что теперь $ 1 не указывает на исходный $ 1 (вариант), а к первому из оставшихся аргументов (те, которые идут после всех параметров и их значений). пример: myrm -foo -bar = baz этот аргумент затем один, затем другой - person Olivier Dulac; 13.04.2018

Принятый ответ очень хорошо показывает все недостатки встроенного getopts в bash. Ответ заканчивается:

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

И хотя я в принципе согласен с этим утверждением, я считаю, что количество раз, когда мы все реализовывали эту функцию в различных сценариях, оправдывает приложенные усилия для создания «стандартизированного», хорошо протестированного решения.

Таким образом, я "обновил" встроенный в getopts bash, реализовав getopts_long в чистом bash, без внешних зависимостей. Использование функции на 100% совместимо со встроенным getopts.

Включив getopts_long (который размещен на GitHub) в сценарий, ответ на исходный вопрос может быть реализовано так просто, как:

source "${PATH_TO}/getopts_long.bash"

while getopts_long ':c: copyfile:' OPTKEY; do
    case ${OPTKEY} in
        'c'|'copyfile')
            echo 'file supplied -- ${OPTARG}'
            ;;
        '?')
            echo "INVALID OPTION -- ${OPTARG}" >&2
            exit 1
            ;;
        ':')
            echo "MISSING ARGUMENT for option -- ${OPTARG}" >&2
            exit 1
            ;;
        *)
            echo "Misconfigured OPTSPEC or uncaught option -- ${OPTKEY}" >&2
            exit 1
            ;;
    esac
done

shift $(( OPTIND - 1 ))
[[ "${1}" == "--" ]] && shift
person UrsaDK    schedule 13.01.2019

У меня еще недостаточно репутации, чтобы прокомментировать или проголосовать за его решение, но ответ компании мне очень понравился. Единственная проблема, с которой я столкнулся, заключалась в том, что аргументы заключались в одинарные кавычки (так что я их убираю).

Я также добавил несколько примеров использования и текст HELP. Я включил сюда свою слегка расширенную версию:

#!/bin/bash

# getopt example
# from: https://stackoverflow.com/questions/402377/using-getopts-in-bash-shell-script-to-get-long-and-short-command-line-options
HELP_TEXT=\
"   USAGE:\n
    Accepts - and -- flags, can specify options that require a value, and can be in any order. A double-hyphen (--) will stop processing options.\n\n

    Accepts the following forms:\n\n

    getopt-example.sh -a -b -c value-for-c some-arg\n
    getopt-example.sh -c value-for-c -a -b some-arg\n
    getopt-example.sh -abc some-arg\n
    getopt-example.sh --along --blong --clong value-for-c -a -b -c some-arg\n
    getopt-example.sh some-arg --clong value-for-c\n
    getopt-example.sh
"

aflag=false
bflag=false
cargument=""

# options may be followed by one colon to indicate they have a required argument
if ! options=$(getopt -o abc:h\? -l along,blong,help,clong: -- "$@")
then
    # something went wrong, getopt will put out an error message for us
    exit 1
fi

set -- $options

while [ $# -gt 0 ]
do
    case $1 in
    -a|--along) aflag=true ;;
    -b|--blong) bflag=true ;;
    # for options with required arguments, an additional shift is required
    -c|--clong) cargument="$2" ; shift;;
    -h|--help|-\?) echo -e $HELP_TEXT; exit;;
    (--) shift; break;;
    (-*) echo "$0: error - unrecognized option $1" 1>&2; exit 1;;
    (*) break;;
    esac
    shift
done

# to remove the single quotes around arguments, pipe the output into:
# | sed -e "s/^'\\|'$//g"  (just leading/trailing) or | sed -e "s/'//g"  (all)

echo aflag=${aflag}
echo bflag=${bflag}
echo cargument=${cargument}

while [ $# -gt 0 ]
do
    echo arg=$1
    shift

    if [[ $aflag == true ]]; then
        echo a is true
    fi

done
person kghastie    schedule 12.06.2012
comment
Просто используйте -u в качестве аргумента для getopt, и вы получите значения без кавычек. - person JazzCat; 27.08.2020

Здесь вы можете найти несколько различных подходов к синтаксическому анализу сложных параметров в bash: http://mywiki.wooledge.org/ComplexOptionParsing < / а>

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

#!/bin/bash
# Uses bash extensions.  Not portable as written.

declare -A longoptspec
longoptspec=( [loglevel]=1 ) #use associative array to declare how many arguments a long option expects, in this case we declare that loglevel expects/has one argument, long options that aren't listed i n this way will have zero arguments by default
optspec=":h-:"
while getopts "$optspec" opt; do
while true; do
    case "${opt}" in
        -) #OPTARG is name-of-long-option or name-of-long-option=value
            if [[ "${OPTARG}" =~ .*=.* ]] #with this --key=value format only one argument is possible
            then
                opt=${OPTARG/=*/}
                OPTARG=${OPTARG#*=}
                ((OPTIND--))    
            else #with this --key value1 value2 format multiple arguments are possible
                opt="$OPTARG"
                OPTARG=(${@:OPTIND:$((longoptspec[$opt]))})
            fi
            ((OPTIND+=longoptspec[$opt]))
            continue #now that opt/OPTARG are set we can process them as if getopts would've given us long options
            ;;
        loglevel)
          loglevel=$OPTARG
            ;;
        h|help)
            echo "usage: $0 [--loglevel[=]<value>]" >&2
            exit 2
            ;;
    esac
break; done
done

# End of file
person user3573558    schedule 01.05.2014

Я работал над этой темой довольно долгое время ... и создал свою собственную библиотеку, которую вам нужно будет использовать в качестве источника в вашем основном скрипте. См. libopt4shell и cd2mpc в качестве примера. Надеюсь, это поможет !

person liealgebra    schedule 11.03.2011

Улучшенное решение:

# translate long options to short
# Note: This enable long options but disable "--?*" in $OPTARG, or disable long options after  "--" in option fields.
for ((i=1;$#;i++)) ; do
    case "$1" in
        --)
            # [ ${args[$((i-1))]} == ... ] || EndOpt=1 ;;& # DIRTY: we still can handle some execptions...
            EndOpt=1 ;;&
        --version) ((EndOpt)) && args[$i]="$1"  || args[$i]="-V";;
        # default case : short option use the first char of the long option:
        --?*) ((EndOpt)) && args[$i]="$1"  || args[$i]="-${1:2:1}";;
        # pass through anything else:
        *) args[$i]="$1" ;;
    esac
    shift
done
# reset the translated args
set -- "${args[@]}"

function usage {
echo "Usage: $0 [options] files" >&2
    exit $1
}

# now we can process with getopt
while getopts ":hvVc:" opt; do
    case $opt in
        h)  usage ;;
        v)  VERBOSE=true ;;
        V)  echo $Version ; exit ;;
        c)  source $OPTARG ;;
        \?) echo "unrecognized option: -$opt" ; usage -1 ;;
        :)
        echo "option -$OPTARG requires an argument"
        usage -1
        ;;
    esac
done

shift $((OPTIND-1))
[[ "$1" == "--" ]] && shift
person jbar    schedule 15.07.2011

Может быть, проще использовать ksh, только для части getopts, если нужны длинные параметры командной строки, так как там это проще сделать.

# Working Getopts Long => KSH

#! /bin/ksh
# Getopts Long
USAGE="s(showconfig)"
USAGE+="c:(createdb)"
USAGE+="l:(createlistener)"
USAGE+="g:(generatescripts)"
USAGE+="r:(removedb)"
USAGE+="x:(removelistener)"
USAGE+="t:(createtemplate)"
USAGE+="h(help)"

while getopts "$USAGE" optchar ; do
    case $optchar in
    s)  echo "Displaying Configuration" ;;
        c)  echo "Creating Database $OPTARG" ;;
    l)  echo "Creating Listener LISTENER_$OPTARG" ;;
    g)  echo "Generating Scripts for Database $OPTARG" ;;
    r)  echo "Removing Database $OPTARG" ;;
    x)  echo "Removing Listener LISTENER_$OPTARG" ;;
    t)  echo "Creating Database Template" ;;
    h)  echo "Help" ;;
    esac
done
person efstathiou_e    schedule 26.06.2012
comment
+1 - Обратите внимание, что это ограничено ksh93 - из проекта AST с открытым исходным кодом (AT&T Research). - person Henk Langeveld; 16.08.2012

Мне нужно что-то без внешних зависимостей, со строгой поддержкой bash (-u), и мне нужно, чтобы это работало даже с более старыми версиями bash. Это обрабатывает различные типы параметров:

  • короткие буллы (-h)
  • короткие параметры (-i "image.jpg")
  • длинные булевые (--help)
  • параметры равно (--file = "filename.ext")
  • параметры пробела (--file "filename.ext")
  • конкатенированные булевые (-hvm)

Просто вставьте следующее вверху вашего скрипта:

# Check if a list of params contains a specific param
# usage: if _param_variant "h|?|help p|path f|file long-thing t|test-thing" "file" ; then ...
# the global variable $key is updated to the long notation (last entry in the pipe delineated list, if applicable)
_param_variant() {
  for param in $1 ; do
    local variants=${param//\|/ }
    for variant in $variants ; do
      if [[ "$variant" = "$2" ]] ; then
        # Update the key to match the long version
        local arr=(${param//\|/ })
        let last=${#arr[@]}-1
        key="${arr[$last]}"
        return 0
      fi
    done
  done
  return 1
}

# Get input parameters in short or long notation, with no dependencies beyond bash
# usage:
#     # First, set your defaults
#     param_help=false
#     param_path="."
#     param_file=false
#     param_image=false
#     param_image_lossy=true
#     # Define allowed parameters
#     allowed_params="h|?|help p|path f|file i|image image-lossy"
#     # Get parameters from the arguments provided
#     _get_params $*
#
# Parameters will be converted into safe variable names like:
#     param_help,
#     param_path,
#     param_file,
#     param_image,
#     param_image_lossy
#
# Parameters without a value like "-h" or "--help" will be treated as
# boolean, and will be set as param_help=true
#
# Parameters can accept values in the various typical ways:
#     -i "path/goes/here"
#     --image "path/goes/here"
#     --image="path/goes/here"
#     --image=path/goes/here
# These would all result in effectively the same thing:
#     param_image="path/goes/here"
#
# Concatinated short parameters (boolean) are also supported
#     -vhm is the same as -v -h -m
_get_params(){

  local param_pair
  local key
  local value
  local shift_count

  while : ; do
    # Ensure we have a valid param. Allows this to work even in -u mode.
    if [[ $# == 0 || -z $1 ]] ; then
      break
    fi

    # Split the argument if it contains "="
    param_pair=(${1//=/ })
    # Remove preceeding dashes
    key="${param_pair[0]#--}"

    # Check for concatinated boolean short parameters.
    local nodash="${key#-}"
    local breakout=false
    if [[ "$nodash" != "$key" && ${#nodash} -gt 1 ]]; then
      # Extrapolate multiple boolean keys in single dash notation. ie. "-vmh" should translate to: "-v -m -h"
      local short_param_count=${#nodash}
      let new_arg_count=$#+$short_param_count-1
      local new_args=""
      # $str_pos is the current position in the short param string $nodash
      for (( str_pos=0; str_pos<new_arg_count; str_pos++ )); do
        # The first character becomes the current key
        if [ $str_pos -eq 0 ] ; then
          key="${nodash:$str_pos:1}"
          breakout=true
        fi
        # $arg_pos is the current position in the constructed arguments list
        let arg_pos=$str_pos+1
        if [ $arg_pos -gt $short_param_count ] ; then
          # handle other arguments
          let orignal_arg_number=$arg_pos-$short_param_count+1
          local new_arg="${!orignal_arg_number}"
        else
          # break out our one argument into new ones
          local new_arg="-${nodash:$str_pos:1}"
        fi
        new_args="$new_args \"$new_arg\""
      done
      # remove the preceding space and set the new arguments
      eval set -- "${new_args# }"
    fi
    if ! $breakout ; then
      key="$nodash"
    fi

    # By default we expect to shift one argument at a time
    shift_count=1
    if [ "${#param_pair[@]}" -gt "1" ] ; then
      # This is a param with equals notation
      value="${param_pair[1]}"
    else
      # This is either a boolean param and there is no value,
      # or the value is the next command line argument
      # Assume the value is a boolean true, unless the next argument is found to be a value.
      value=true
      if [[ $# -gt 1 && -n "$2" ]]; then
        local nodash="${2#-}"
        if [ "$nodash" = "$2" ]; then
          # The next argument has NO preceding dash so it is a value
          value="$2"
          shift_count=2
        fi
      fi
    fi

    # Check that the param being passed is one of the allowed params
    if _param_variant "$allowed_params" "$key" ; then
      # --key-name will now become param_key_name
      eval param_${key//-/_}="$value"
    else
      printf 'WARNING: Unknown option (ignored): %s\n' "$1" >&2
    fi
    shift $shift_count
  done
}

И используйте это так:

# Assign defaults for parameters
param_help=false
param_path=$(pwd)
param_file=false
param_image=true
param_image_lossy=true
param_image_lossy_quality=85

# Define the params we will allow
allowed_params="h|?|help p|path f|file i|image image-lossy image-lossy-quality"

# Get the params from arguments provided
_get_params $*
person Heath Dutton    schedule 02.07.2015

если просто так вы хотите вызвать скрипт

myscript.sh --input1 "ABC" --input2 "PQR" --input2 "XYZ"

то вы можете следовать этому простейшему способу добиться этого с помощью getopt и --longoptions

попробуйте это, надеюсь, это будет полезно

# Read command line options
ARGUMENT_LIST=(
    "input1"
    "input2"
    "input3"
)



# read arguments
opts=$(getopt \
    --longoptions "$(printf "%s:," "${ARGUMENT_LIST[@]}")" \
    --name "$(basename "$0")" \
    --options "" \
    -- "$@"
)


echo $opts

eval set --$opts

while true; do
    case "$1" in
    --input1)  
        shift
        empId=$1
        ;;
    --input2)  
        shift
        fromDate=$1
        ;;
    --input3)  
        shift
        toDate=$1
        ;;
      --)
        shift
        break
        ;;
    esac
    shift
done
person Ashish Shetkar    schedule 02.04.2019
comment
как добавить короткие варианты? - person GoingMyWay; 25.06.2021

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

Вот как:

$ cat > longopt
while getopts 'e:-:' OPT; do
  case $OPT in
    e) echo echo: $OPTARG;;
    -) #long option
       case $OPTARG in
         long-option) echo long option;;
         *) echo long option: $OPTARG;;
       esac;;
  esac
done

$ bash longopt -e asd --long-option --long1 --long2 -e test
echo: asd
long option
long option: long1
long option: long2
echo: test

Если вы попытаетесь использовать OPTIND для получения параметра для длинной опции, getopts будет рассматривать его как первый необязательный позиционный параметр и прекратит синтаксический анализ любых других параметров. В таком случае вам будет лучше обработать его вручную с помощью простого оператора case.

Это будет работать «всегда»:

$ cat >longopt2
while (($#)); do
    OPT=$1
    shift
    case $OPT in
        --*) case ${OPT:2} in
            long1) echo long1 option;;
            complex) echo comples with argument $1; shift;;
        esac;;

        -*) case ${OPT:1} in
            a) echo short option a;;
            b) echo short option b with parameter $1; shift;;
        esac;;
    esac
done


$ bash longopt2 --complex abc -a --long -b test
comples with argument abc
short option a
short option b with parameter test

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

Но это вариант.

person estani    schedule 01.02.2012
comment
Но длинные варианты часто требуют аргументов. И вы могли бы сделать больше с -, чтобы заставить его работать, даже если это что-то вроде взлома. В конце концов, можно возразить, что если он не поддерживает его изначально, то каждый способ его реализации - это что-то вроде взлома, но, тем не менее, вы также можете расширить -. И да, shift очень полезен, но, конечно, если он ожидает аргумент, он может закончить тем, что следующий аргумент (если он не указан пользователем) является частью ожидаемого аргумента. - person Pryftan; 13.08.2019
comment
да, это poc для длинного имени аргумента без аргументов, чтобы различать между ними, вам нужна какая-то конфигурация, например getops. А что касается сдвига, вы всегда можете вернуть его с набором. В любом случае он должен быть настраиваемым, если параметр ожидается или нет. Вы даже можете использовать для этого некоторую магию, но тогда вы заставите пользователей использовать - чтобы сигнализировать, что магия прекращается и начинаются позиционные параметры, что хуже, imho. - person estani; 14.08.2019
comment
Справедливо. Это более чем разумно. Tbh я даже не помню, о чем я, и я полностью забыл об этом вопросе. Я только смутно помню, как я его вообще нашел. Ваше здоровье. О, и есть +1 за эту идею. Вы приложили усилия и также разъяснили, к чему вы клоните. Я уважаю людей, которые прилагают усилия, чтобы дать другим идеи и т. Д. - person Pryftan; 09.09.2019

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

Мне очень легко пользоваться, вот пример:

ArgParser::addArg "[h]elp"    false    "This list"
ArgParser::addArg "[q]uiet"   false    "Supress output"
ArgParser::addArg "[s]leep"   1        "Seconds to sleep"
ArgParser::addArg "v"         1        "Verbose mode"

ArgParser::parse "$@"

ArgParser::isset help && ArgParser::showArgs

ArgParser::isset "quiet" \
   && echo "Quiet!" \
   || echo "Noisy!"

local __sleep
ArgParser::tryAndGetArg sleep into __sleep \
   && echo "Sleep for $__sleep seconds" \
   || echo "No value passed for sleep"

# This way is often more convienient, but is a little slower
echo "Sleep set to: $( ArgParser::getArg sleep )"

Требуемый BASH немного длиннее, чем мог бы быть, но я хотел избежать использования ассоциативных массивов BASH 4. Вы также можете загрузить это прямо с http://nt4.com/bash/argparser.inc.sh < / а>

#!/usr/bin/env bash

# Updates to this script may be found at
# http://nt4.com/bash/argparser.inc.sh

# Example of runtime usage:
# mnc.sh --nc -q Caprica.S0*mkv *.avi *.mp3 --more-options here --host centos8.host.com

# Example of use in script (see bottom)
# Just include this file in yours, or use
# source argparser.inc.sh

unset EXPLODED
declare -a EXPLODED
function explode 
{
    local c=$# 
    (( c < 2 )) && 
    {
        echo function "$0" is missing parameters 
        return 1
    }

    local delimiter="$1"
    local string="$2"
    local limit=${3-99}

    local tmp_delim=$'\x07'
    local delin=${string//$delimiter/$tmp_delim}
    local oldifs="$IFS"

    IFS="$tmp_delim"
    EXPLODED=($delin)
    IFS="$oldifs"
}


# See: http://fvue.nl/wiki/Bash:_Passing_variables_by_reference
# Usage: local "$1" && upvar $1 "value(s)"
upvar() {
    if unset -v "$1"; then           # Unset & validate varname
        if (( $# == 2 )); then
            eval $1=\"\$2\"          # Return single value
        else
            eval $1=\(\"\${@:2}\"\)  # Return array
        fi
    fi
}

function decho
{
    :
}

function ArgParser::check
{
    __args=${#__argparser__arglist[@]}
    for (( i=0; i<__args; i++ ))
    do
        matched=0
        explode "|" "${__argparser__arglist[$i]}"
        if [ "${#1}" -eq 1 ]
        then
            if [ "${1}" == "${EXPLODED[0]}" ]
            then
                decho "Matched $1 with ${EXPLODED[0]}"
                matched=1

                break
            fi
        else
            if [ "${1}" == "${EXPLODED[1]}" ]
            then
                decho "Matched $1 with ${EXPLODED[1]}"
                matched=1

                break
            fi
        fi
    done
    (( matched == 0 )) && return 2
    # decho "Key $key has default argument of ${EXPLODED[3]}"
    if [ "${EXPLODED[3]}" == "false" ]
    then
        return 0
    else
        return 1
    fi
}

function ArgParser::set
{
    key=$3
    value="${1:-true}"
    declare -g __argpassed__$key="$value"
}

function ArgParser::parse
{

    unset __argparser__argv
    __argparser__argv=()
    # echo parsing: "$@"

    while [ -n "$1" ]
    do
        # echo "Processing $1"
        if [ "${1:0:2}" == '--' ]
        then
            key=${1:2}
            value=$2
        elif [ "${1:0:1}" == '-' ]
        then
            key=${1:1}               # Strip off leading -
            value=$2
        else
            decho "Not argument or option: '$1'" >& 2
            __argparser__argv+=( "$1" )
            shift
            continue
        fi
        # parameter=${tmp%%=*}     # Extract name.
        # value=${tmp##*=}         # Extract value.
        decho "Key: '$key', value: '$value'"
        # eval $parameter=$value
        ArgParser::check $key
        el=$?
        # echo "Check returned $el for $key"
        [ $el -eq  2 ] && decho "No match for option '$1'" >&2 # && __argparser__argv+=( "$1" )
        [ $el -eq  0 ] && decho "Matched option '${EXPLODED[2]}' with no arguments"        >&2 && ArgParser::set true "${EXPLODED[@]}"
        [ $el -eq  1 ] && decho "Matched option '${EXPLODED[2]}' with an argument of '$2'"   >&2 && ArgParser::set "$2" "${EXPLODED[@]}" && shift
        shift
    done
}

function ArgParser::isset
{
    declare -p "__argpassed__$1" > /dev/null 2>&1 && return 0
    return 1
}

function ArgParser::getArg
{
    # This one would be a bit silly, since we can only return non-integer arguments ineffeciently
    varname="__argpassed__$1"
    echo "${!varname}"
}

##
# usage: tryAndGetArg <argname> into <varname>
# returns: 0 on success, 1 on failure
function ArgParser::tryAndGetArg
{
    local __varname="__argpassed__$1"
    local __value="${!__varname}"
    test -z "$__value" && return 1
    local "$3" && upvar $3 "$__value"
    return 0
}

function ArgParser::__construct
{
    unset __argparser__arglist
    # declare -a __argparser__arglist
}

##
# @brief add command line argument
# @param 1 short and/or long, eg: [s]hort
# @param 2 default value
# @param 3 description
##
function ArgParser::addArg
{
    # check for short arg within long arg
    if [[ "$1" =~ \[(.)\] ]]
    then
        short=${BASH_REMATCH[1]}
        long=${1/\[$short\]/$short}
    else
        long=$1
    fi
    if [ "${#long}" -eq 1 ]
    then
        short=$long
        long=''
    fi
    decho short: "$short"
    decho long: "$long"
    __argparser__arglist+=("$short|$long|$1|$2|$3")
}

## 
# @brief show available command line arguments
##
function ArgParser::showArgs
{
    # declare -p | grep argparser
    printf "Usage: %s [OPTION...]\n\n" "$( basename "${BASH_SOURCE[0]}" )"
    printf "Defaults for the options are specified in brackets.\n\n";

    __args=${#__argparser__arglist[@]}
    for (( i=0; i<__args; i++ ))
    do
        local shortname=
        local fullname=
        local default=
        local description=
        local comma=

        explode "|" "${__argparser__arglist[$i]}"

        shortname="${EXPLODED[0]:+-${EXPLODED[0]}}" # String Substitution Guide: 
        fullname="${EXPLODED[1]:+--${EXPLODED[1]}}" # http://tldp.org/LDP/abs/html/parameter-substitution.html
        test -n "$shortname" \
            && test -n "$fullname" \
            && comma=","

        default="${EXPLODED[3]}"
        case $default in
            false )
                default=
                ;;
            "" )
                default=
                ;;
            * )
                default="[$default]"
        esac

        description="${EXPLODED[4]}"

        printf "  %2s%1s %-19s %s %s\n" "$shortname" "$comma" "$fullname" "$description" "$default"
    done
}

function ArgParser::test
{
    # Arguments with a default of 'false' do not take paramaters (note: default
    # values are not applied in this release)

    ArgParser::addArg "[h]elp"      false       "This list"
    ArgParser::addArg "[q]uiet" false       "Supress output"
    ArgParser::addArg "[s]leep" 1           "Seconds to sleep"
    ArgParser::addArg "v"           1           "Verbose mode"

    ArgParser::parse "$@"

    ArgParser::isset help && ArgParser::showArgs

    ArgParser::isset "quiet" \
        && echo "Quiet!" \
        || echo "Noisy!"

    local __sleep
    ArgParser::tryAndGetArg sleep into __sleep \
        && echo "Sleep for $__sleep seconds" \
        || echo "No value passed for sleep"

    # This way is often more convienient, but is a little slower
    echo "Sleep set to: $( ArgParser::getArg sleep )"

    echo "Remaining command line: ${__argparser__argv[@]}"

}

if [ "$( basename "$0" )" == "argparser.inc.sh" ]
then
    ArgParser::test "$@"
fi
person Orwellophile    schedule 16.03.2013

Если все ваши длинные параметры имеют уникальные и совпадающие первые символы в качестве коротких параметров, например,

./slamm --chaos 23 --plenty test -quiet

Такой же как

./slamm -c 23 -p test -q

Вы можете использовать эти перед getopts для перезаписи $ args:

# change long options to short options

for arg; do 
    [[ "${arg:0:1}" == "-" ]] && delim="" || delim="\""
    if [ "${arg:0:2}" == "--" ]; 
       then args="${args} -${arg:2:1}" 
       else args="${args} ${delim}${arg}${delim}"
    fi
done

# reset the incoming args
eval set -- $args

# proceed as usual
while getopts ":b:la:h" OPTION; do
    .....

Спасибо за мтвее за вдохновение ;-)

person commonpike    schedule 06.07.2012
comment
Я не понимаю здесь значения eval - person user.friendly; 16.06.2017

Встроенный getopts анализирует только короткие параметры (кроме ksh93), но вы все равно можете добавить несколько строк сценария, чтобы getopts обрабатывал длинные параметры.

Вот часть кода, найденного в http://www.uxora.com/unix/shell-script/22-handle-long-options-with-getopts

  #== set short options ==#
SCRIPT_OPTS=':fbF:B:-:h'
  #== set long options associated with short one ==#
typeset -A ARRAY_OPTS
ARRAY_OPTS=(
    [foo]=f
    [bar]=b
    [foobar]=F
    [barfoo]=B
    [help]=h
    [man]=h
)

  #== parse options ==#
while getopts ${SCRIPT_OPTS} OPTION ; do
    #== translate long options to short ==#
    if [[ "x$OPTION" == "x-" ]]; then
        LONG_OPTION=$OPTARG
        LONG_OPTARG=$(echo $LONG_OPTION | grep "=" | cut -d'=' -f2)
        LONG_OPTIND=-1
        [[ "x$LONG_OPTARG" = "x" ]] && LONG_OPTIND=$OPTIND || LONG_OPTION=$(echo $OPTARG | cut -d'=' -f1)
        [[ $LONG_OPTIND -ne -1 ]] && eval LONG_OPTARG="\$$LONG_OPTIND"
        OPTION=${ARRAY_OPTS[$LONG_OPTION]}
        [[ "x$OPTION" = "x" ]] &&  OPTION="?" OPTARG="-$LONG_OPTION"

        if [[ $( echo "${SCRIPT_OPTS}" | grep -c "${OPTION}:" ) -eq 1 ]]; then
            if [[ "x${LONG_OPTARG}" = "x" ]] || [[ "${LONG_OPTARG}" = -* ]]; then 
                OPTION=":" OPTARG="-$LONG_OPTION"
            else
                OPTARG="$LONG_OPTARG";
                if [[ $LONG_OPTIND -ne -1 ]]; then
                    [[ $OPTIND -le $Optnum ]] && OPTIND=$(( $OPTIND+1 ))
                    shift $OPTIND
                    OPTIND=1
                fi
            fi
        fi
    fi

    #== options follow by another option instead of argument ==#
    if [[ "x${OPTION}" != "x:" ]] && [[ "x${OPTION}" != "x?" ]] && [[ "${OPTARG}" = -* ]]; then 
        OPTARG="$OPTION" OPTION=":"
    fi

    #== manage options ==#
    case "$OPTION" in
        f  ) foo=1 bar=0                    ;;
        b  ) foo=0 bar=1                    ;;
        B  ) barfoo=${OPTARG}               ;;
        F  ) foobar=1 && foobar_name=${OPTARG} ;;
        h ) usagefull && exit 0 ;;
        : ) echo "${SCRIPT_NAME}: -$OPTARG: option requires an argument" >&2 && usage >&2 && exit 99 ;;
        ? ) echo "${SCRIPT_NAME}: -$OPTARG: unknown option" >&2 && usage >&2 && exit 99 ;;
    esac
done
shift $((${OPTIND} - 1))

Вот тест:

# Short options test
$ ./foobar_any_getopts.sh -bF "Hello world" -B 6 file1 file2
foo=0 bar=1
barfoo=6
foobar=1 foobar_name=Hello world
files=file1 file2

# Long and short options test
$ ./foobar_any_getopts.sh --bar -F Hello --barfoo 6 file1 file2
foo=0 bar=1
barfoo=6
foobar=1 foobar_name=Hello
files=file1 file2

В противном случае в последней версии Korn Shell ksh93 getopts может естественно анализировать длинные параметры и даже отображать одинаковую страницу руководства. (См. http://www.uxora.com/unix/shell-script/20-getopts-with-man-page-and-long-options)

person Michel VONGVILAY uxora.com    schedule 08.04.2015

Встроенный getopt в OS X (BSD) не поддерживает длинные параметры, но версия GNU поддерживает: brew install gnu-getopt. Затем что-то похожее на: cp /usr/local/Cellar/gnu-getopt/1.1.6/bin/getopt /usr/local/bin/gnu-getopt.

person wprl    schedule 18.05.2016

Простой способ получить только аргументы с длинными именами:

Использовать:

$ ./test-args.sh --a1 a1 --a2 "a 2" --a3 --a4= --a5=a5 --a6="a 6"
a1 = "a1"
a2 = "a 2"
a3 = "TRUE"
a4 = ""
a5 = "a5"
a6 = "a 6"
a7 = ""

Сценарий:

#!/bin/bash

function main() {
    ARGS=`getArgs "$@"`

    a1=`echo "$ARGS" | getNamedArg a1`
    a2=`echo "$ARGS" | getNamedArg a2`
    a3=`echo "$ARGS" | getNamedArg a3`
    a4=`echo "$ARGS" | getNamedArg a4`
    a5=`echo "$ARGS" | getNamedArg a5`
    a6=`echo "$ARGS" | getNamedArg a6`
    a7=`echo "$ARGS" | getNamedArg a7`

    echo "a1 = \"$a1\""
    echo "a2 = \"$a2\""
    echo "a3 = \"$a3\""
    echo "a4 = \"$a4\""
    echo "a5 = \"$a5\""
    echo "a6 = \"$a6\""
    echo "a7 = \"$a7\""

    exit 0
}


function getArgs() {
    for arg in "$@"; do
        echo "$arg"
    done
}


function getNamedArg() {
    ARG_NAME=$1

    sed --regexp-extended --quiet --expression="
        s/^--$ARG_NAME=(.*)\$/\1/p  # Get arguments in format '--arg=value': [s]ubstitute '--arg=value' by 'value', and [p]rint
        /^--$ARG_NAME\$/ {          # Get arguments in format '--arg value' ou '--arg'
            n                       # - [n]ext, because in this format, if value exists, it will be the next argument
            /^--/! p                # - If next doesn't starts with '--', it is the value of the actual argument
            /^--/ {                 # - If next do starts with '--', it is the next argument and the actual argument is a boolean one
                # Then just repla[c]ed by TRUE
                c TRUE
            }
        }
    "
}


main "$@"
person lmcarreiro    schedule 11.02.2019

hm.

не очень доволен вариантами чистого bash. почему бы не использовать Perl, чтобы получить то, что вы хотите. Непосредственно проанализируйте массив $ * и автоматически назовите свои параметры.

простой вспомогательный скрипт:

#!/usr/bin/perl
use Getopt::Long;

my $optstring = shift;

my @opts = split(m#,#, $optstring);

my %opt;
GetOptions(\%opt, @opts);

print "set -- " . join(' ', map("'$_'", @ARGV)) . ";";
my $xx;

my $key;
foreach $key (keys(%opt))
{
    print "export $key='$opt{$key}'; ";
}

то вы можете использовать в своем скрипте как однострочник, например:

#!/bin/bash

eval `getopts.pl reuse:s,long_opt:s,hello $*`;

echo "HELLO: $hello"
echo "LONG_OPT: $long_opt"
echo "REUSE: $reuse"

echo $*

/tmp/script.sh hello --reuse me --long_opt any_you_want_except_spaces --hello 1 2 3

ПРИВЕТ: 1 LONG_OPT: something_you_want_except пробелы ПОВТОРНОЕ ИСПОЛЬЗОВАНИЕ: меня

1 2 3

Единственная оговорка - пробелы не работают. Но он избегает довольно сложного синтаксиса циклов bash, работает с длинными аргументами, автоматически называет их как переменные и автоматически изменяет размер $ *, поэтому будет работать в 99% случаев.

person Edward Peschko    schedule 19.05.2015
comment
Это ошибочно. Вам нужно использовать "$@", а не $*, чтобы правильно сохранить пробелы и другие метасимволы оболочки в списке аргументов. (Я больше не исследовал код.) - person tripleee; 19.05.2015
comment
Почему бы не использовать perl, потому что он добавляет уровень сложности (еще одна вещь, на которую можно положиться). Я не могу сказать с уверенностью в своей усталой голове, но я бы также подумал, что это было бы менее эффективно. Лично я не думаю, что bash имеет сложный синтаксис цикла, хотя, по иронии судьбы, я не использую синтаксис, аналогичный C, хотя C, безусловно, является моим любимым для работы. Я не знаю, совместима ли это с POSIX, это одна из причин, по которым я думаю, но и другой способ тоже хорош. Конечно, снаряды в чем-то различаются, но все же. Тем не менее, безусловно, можно использовать другой язык, но не обязательно идеальный. - person Pryftan; 13.08.2019

person    schedule
comment
Объяснение было бы неплохим. Первый сценарий принимает короткие параметры только тогда, когда второй сценарий имеет ошибку при синтаксическом анализе аргументов с длинными параметрами; его переменная должна быть "${1:0:1}" для аргумента № 1, подстрока с индексом 0, длина 1. Это не позволяет смешивать короткие и длинные варианты. - person Adam Katz; 24.06.2016