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

410

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

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

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

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

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

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

Ответы:

304

Есть три реализации, которые можно рассмотреть:

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

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

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


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

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

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

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

Билл Карвин
источник
18
Так. Что такое кроссплатформенное, портативное решение?
troelskn
6
GNU Getopt кажется единственным выбором. На Mac установите GNU getopt из macports. В Windows я бы установил GNU getopt с Cygwin.
Билл Карвин
2
Очевидно , ksh getopts может обрабатывать длинные варианты.
Tgr
1
@ Bill +1, хотя также довольно просто собрать getopt из исходного кода ( software.frodo.looijaard.name/getopt ) на Mac. Вы также можете проверить версию getopt, установленную в вашей системе, из скриптов с помощью "getopt -T; echo $?".
Чиназавр
8
@Bill Karwin: «Встроенная команда bash getopts не поддерживает длинные имена опций с префиксом двойной тире». Но можно сделать getopts для поддержки длинных опций: см. Stackoverflow.com/a/7680682/915044 ниже.
TomRoche
307

getoptи getoptsэто разные звери, и люди, кажется, немного не понимают, что они делают. getoptsявляется встроенной командой для bashобработки параметров командной строки в цикле и назначения каждой найденной опции и значения по очереди встроенным переменным, чтобы вы могли в дальнейшем обрабатывать их. getoptоднако это внешняя служебная программа, которая фактически не обрабатывает ваши параметры так , как это делают , например, bash getopts, Getoptмодуль Perl или модули Python optparse/ argparse. Все, что 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 На самом деле, getoptsin ksh93поддерживает параметры с длинными именами, но эта оболочка используется не так часто, как bash. В zsh, используйте, zparseoptsчтобы получить эту функциональность.

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

Городской бродяга
источник
3
Это было очень полезно, идея использования getopt для проверки параметров и последующей обработки этих параметров в очень простом цикле работала очень хорошо, когда я хотел добавить опции длинного стиля в скрипт bash. Спасибо.
Янмьонес
2
getoptв Linux это не утилита GNU, а традиционная getoptизначально не из BSD, а из AT & T Unix. ksh93 getopts(также из AT & T) поддерживает длинные опции в стиле GNU.
Стефан Шазелас
@StephaneChazelas - отредактировано, чтобы отразить ваши комментарии. Я по-прежнему предпочитаю термин «GNU getopt», хотя это и неправильное название, потому что эта версия следует соглашениям GNU и в целом действует как программа GNU (например, с использованием POSIXLY_CORRECT), в то время как «Getopt с расширенными возможностями Linux» ошибочно предполагает, что эта версия существует только на Linux.
Urban Vagabond
1
Он поставляется из пакета util-linux, так что это только Linux, так как этот пакет программного обеспечения предназначен только для Linux (который getoptможет быть легко перенесен на другие Unices, но многие другие программы для util-linuxLinux специфичны для Linux). Все не-GNU программы, использующие GNU getopt (3), понимают $POSIX_CORRECT. Например, вы бы не сказали, что aplayGNU именно по этим причинам. Я подозреваю, что когда FreeBSD упоминает GNU getopt, они имеют в виду GNU getopt (3) C API.
Стефан Шазелас
@StephaneChazelas - во FreeBSD есть сообщение об ошибке «Зависимость сборки: установите GNU getopt», которое однозначно ссылается на getoptutil, а не на getopt (3).
Urban Vagabond
202

Встроенную функцию Bash getopts можно использовать для анализа длинных опций, поместив символ тире, за которым следует двоеточие, в 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 , этот прием использует нестандартное поведение оболочки, которое допускает аргумент option (то есть имя файла в «-f filename») быть присоединенным к опции (как в "-ffilename"). Стандарт POSIX гласит, что между ними должен быть пробел, который в случае «- longoption» завершит разбор параметров и превратит все longoptions в аргументы, не являющиеся параметрами.

Арвид Требуется
источник
2
Один вопрос: какова семантика !в val="${!OPTIND}?
TomRoche
2
@TomRoche - это косвенная замена: unix.stackexchange.com/a/41293/84316
ecbrodie
2
@ecbrodie: Это потому, что фактически обработаны два аргумента, а не один. Первый аргумент - это слово «loglevel», а следующий - аргумент для этого аргумента. Между тем, getoptsавтоматически увеличивается только OPTINDна 1, но в нашем случае нам нужно увеличить его на 2, поэтому мы увеличиваем его на 1 вручную, а затем getoptsавтоматически увеличиваем на 1 для нас.
Виктор Заманян
3
Поскольку мы находимся здесь в состоянии равновесия Bash: голые имена переменных допускаются внутри арифметических выражений, в этом нет $необходимости. OPTIND=$(( $OPTIND + 1 ))может быть просто OPTIND=$(( OPTIND + 1 )). Еще более интересно то, что вы даже можете назначать и увеличивать переменные внутри арифметического выражения, так что можно сокращать его дальше : $(( ++OPTIND ))или даже (( ++OPTIND ))принимать во внимание, что оно ++OPTINDвсегда будет положительным, так что это не приведет к отключению запуска оболочки с -eпараметром. :-) gnu.org/software/bash/manual/html_node/Shell-Arithmetic.html
clacke
3
Почему не --very-badдает предупреждение?
Том Хейл,
148

Встроенная 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модуль лучше, хотя и ограничен тем, что обрабатывает только однобуквенные опции.

Джонатан Леффлер
источник
11
getopt, за исключением версии GNU (которая имеет другое соглашение о вызовах), в корне не работает. Не используйте его. Пожалуйста, используйте ** getopts вместо bash-hackers.org/wiki/doku.php/howto/getopts_tutorial
hendry
9
@hendry - по вашей собственной ссылке: «Обратите внимание, что getopts не может анализировать длинные параметры в стиле GNU (--myoption) или длинные параметры в стиле XF86 (-myoption)!»
Том Ожер
1
Джонатан - вам следует переписать пример для использования eval setс кавычками (см. Мой ответ ниже), чтобы он также корректно работал с GNU getopt (по умолчанию в Linux) и правильно обрабатывал пробелы.
Городской Vagabond
@UrbanVagabond: я не уверен, почему я должен это делать. Вопрос помечен Unix, а не Linux. Я намеренно показываю традиционный механизм, и у него есть проблемы с пробелами в аргументах и ​​т. Д. Вы можете продемонстрировать современную версию для Linux, если хотите, и ваш ответ сделает это. (Я отмечаю, passim, что вы используете ${1+"$@"}странно и расходитесь с тем, что необходимо в современных оболочках, особенно с любой оболочкой, которую вы найдете в Linux. См. Использование $ 1: + "$ @"} в / bin / sh для обсуждение этого обозначения.)
Джонатан Леффлер
Вы должны сделать это, потому eval setчто правильно поступает как с GNU, так и с BSD getopt, в то время как plain setделает правильно только с BSD getopt. Таким образом, вы также можете использовать его, eval setчтобы побудить людей привыкнуть к этому. Кстати, я не поняла, что ${1+"$@"}это больше не нужно. Я должен написать вещи, которые работают как на Mac OS X, так и на Linux - между ними они создают большую переносимость. Я только что проверил и "$@"действительно делать правильные вещи на всех sh, bash, kshи zshпод Mac OS X; конечно под Linux тоже.
Городской Vagabond
78

Вот пример, который на самом деле использует 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
SME
источник
1
Вам следует переписать пример для использования eval setс кавычками (см. Мой ответ ниже), чтобы он также корректно работал с GNU getopt (по умолчанию в Linux) и правильно обрабатывал пробелы.
Городской Vagabond
2
Это использует в getoptто время как вопрос о том, getoptsхотя.
Никлас Берглунд
1
Есть (--, (-*и (*действительные модели? Чем они отличаются от --, -*а *?
Maëlan
1
@ Maëlan - ведущая открывающая скобка не является обязательной, так (--)идентичен --)в caseстрофе. Странно видеть неравномерное отступление и непоследовательное использование этих необязательных начальных скобок, но текущий код ответа выглядит для меня верным.
Адам Кац
59

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

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

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

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

Поскольку в нем используется getoptsвстроенная функция , это решение поддерживает использование, подобное 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 варианта» перед выходом.

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


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

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

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

Это будет достигнуто с помощью одного из этих методов:

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

Адам Кац
источник
Очень хорошее автономное решение. Один вопрос: так как не letter-cнуждается в аргументах, не будет ли его достаточно использовать letter-c)? *кажется излишним.
Филипп Кернс
1
@Arne Позиционные аргументы плохой UX; их трудно понять, а необязательные аргументы довольно беспорядочные. getoptsостанавливается на первом позиционном аргументе, поскольку он не предназначен для работы с ними. Это позволяет суб-команде со своими аргументами, например git diff --color, таким образом , я бы интерпретировать command --foo=moo bar --baz wazкак имеющие в --fooкачестве аргумента commandи в --baz wazкачестве аргумента (с опцией) к barюгу от команды. Это можно сделать с помощью приведенного выше кода. Я отвергаю, --bravo -blahпотому что --bravoтребует аргумента, и неясно, что -blahэто не другой вариант.
Адам Кац
1
Я не согласен с UX: позиционные аргументы полезны и просты, если вы ограничиваете их количество (максимум 2 или 1 плюс N-of-the-type-type). Должна быть возможность чередовать их с аргументами ключевых слов, потому что пользователи затем могут создавать команды шаг за шагом (т.е. ls abc -la).
Арне Бабенхаузерхайде
1
@AdamKatz: я написал небольшую статью с этим: draketo.de/english/free-software/shell-argument-parsing - включает в себя повторное чтение оставшихся аргументов, чтобы поймать опции трейлинга.
Арне Бабенхаузерхайде
1
@ArneBabenhauserheide: я обновил этот ответ для поддержки аргументов, разделенных пробелами. Поскольку это требуется evalв оболочке POSIX, оно перечислено ниже остальной части ответа.
Адам Кац
33

Взгляните на 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.

k0pernikus
источник
2
Это фантастика, но, к сожалению, мой getopt (OS X) не поддерживает пробелы в аргументах: / Интересно, есть ли альтернатива.
Аластер Стюарт
@AlastairStuart - на OS X действительно есть альтернатива. Используйте MacPorts для установки GNU getopt (обычно он устанавливается в / opt / local / bin / getopt).
Urban Vagabond
3
@UrbanVagabond - установка несистемных инструментов по умолчанию, к сожалению, не является приемлемым требованием для достаточно переносимого инструмента.
Аластер Стюарт
@AlastairStuart - посмотрите мой ответ для портативного решения, которое использует встроенные getopts, а не getopt GNU. Это то же самое, что и базовое использование getopts, но с дополнительной итерацией для длинных опций.
Адам Кац
31

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


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

  • foobar -f --bar
  • foobar --foo -b
  • foobar -bf --bar --foobar
  • foobar -fbFBAshorty --bar -FB --arguments = longhorn
  • foobar -fA "текстовый коротышка" -B --arguments = "текстовый лонгхорн"
  • Баш Фубар -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
рапануйского
источник
Разве это не работает с более чем одним длинным аргументом (-). Кажется, только прочитал первый для меня.
Синаэстетик
@Sinaesthetic - Да, я играл с evalподходом для разнесенных аргументов на длинных опциях и нашел его ненадежным для определенных оболочек (хотя я ожидаю, что он будет работать с bash, и в этом случае вам не придется использовать eval). См. Мой ответ о том, как принимать аргументы длинных опций, =и мои отмеченные попытки использования пробела. Мое решение не делает внешние вызовы, в то время как это использует cutнесколько раз.
Адам Кац
24

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

# 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
mtvee
источник
1
Разве это не требует места в каждой $argsпереназначении? Это может быть сделано даже без ошибок, но этот код будет терять пробелы в опциях и аргументах (я не думаю, что $delimтрюк сработает). Вместо этого вы можете запустить set внутри в forцикле , если вы достаточно внимательны , чтобы очистить его только на первой итерации. Вот более безопасная версия без башизмов.
Адам Кац
18

Я вроде решил так:

# 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так запутанно.

Вениамин
источник
1
Кажется, это работает для меня, я не знаю, в чем проблема с этим методом, но он кажется простым, поэтому должна быть причина, по которой все остальные не используют его.
Билли Мун
1
@Billy Да, это просто, потому что я не использую какой-либо сценарий для управления своими параметрами и т. Д. По сути, я преобразую строку аргументов ($ @) в массив и перебираю его. В цикле текущим значением будет ключ, а следующим будет значение. Просто как тот.
1
@ Теодор Я рад, что это было полезно для тебя! Это было и для меня болью. Если вам интересно, вы можете увидеть пример этого в действии здесь: raw.github.com/rafaelrinaldi/swf-to-html/master/swf-to-html.sh
2
Определенно самый простой способ, который я видел. Я немного изменил его, например, используя i = $ (($ i + 1)) вместо expr, но концепция герметична.
Томас Диньян
6
Вы совсем не глупы, но вам может не хватать функции: getopt может распознавать смешанные опции (например, -ltrили -lt -rтак же -l -t -r). Кроме того, он обеспечивает некоторую обработку ошибок и простой способ сдвинуть обработанные параметры после завершения обработки опций.
Оливье Дюлак
14

Если вам не нужна 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функциональность вместе с длинными опциями, это простой способ сделать это.

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

jakesandlund
источник
Кажется, это работает только с одним длинным вариантом за раз, но это удовлетворяет мою потребность. Спасибо!
kingjeffrey
В особом случае, --)кажется, shift ;пропал без вести. На данный момент --он останется первым аргументом без опций.
DGW
Я думаю, что это на самом деле лучший ответ, хотя, как указывает dgw, --опция должна быть shiftтам. Я говорю , что это лучше , потому что альтернативы либо платформы зависимые версии getoptили getopts_longили вы должны заставить короткие варианты , которые будут использоваться только в начале команды (то есть - вы используете getoptsзатем обрабатывать длинные опции впоследствии), в то время как это дает какой - либо заказ и полный контроль.
Харавикк
Этот ответ заставляет меня задуматься, почему у нас есть поток из десятков ответов, чтобы выполнить работу, которую можно выполнить не более чем с помощью этого абсолютно ясного и простого решения, и есть ли причина для миллиарда случаев использования getopt, отличных от доказательства себя.
Флориан Хейгл
11

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

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

Nietzche-Jou
источник
3
Он getoptбыл включен в FreeBSD версии 1.0 в 1993 году и с тех пор является частью FreeBSD. Как таковой, он был принят из FreeBSD 4.x для включения в проект Apple Darwin. Начиная с OS X 10.6.8, страница руководства, включенная Apple, остается точной копией страницы руководства FreeBSD. Так что да, он включен в OS X и другие операционные системы помимо Linux. -1 на этот ответ за дезинформацию.
ghoti
8
#!/bin/bash
while getopts "abc:d:" flag
do
  case $flag in
    a) echo "[getopts:$OPTIND]==> -$flag";;
    b) echo "[getopts:$OPTIND]==> -$flag";;
    c) echo "[getopts:$OPTIND]==> -$flag $OPTARG";;
    d) echo "[getopts:$OPTIND]==> -$flag $OPTARG";;
  esac
done

shift $((OPTIND-1))
echo "[otheropts]==> $@"

exit

,

#!/bin/bash
until [ -z "$1" ]; do
  case $1 in
    "--dlong")
      shift
      if [ "${1:1:0}" != "-" ]
      then
        echo "==> dlong $1"
        shift
      fi;;
    *) echo "==> other $1"; shift;;
  esac
done
exit
3ED
источник
2
Объяснение было бы хорошо. Первый скрипт принимает короткие опции только тогда, когда во втором скрипте есть ошибка при разборе аргумента длинной опции; его переменная должна быть "${1:0:1}"для аргумента # 1, подстрока с индексом 0, длина 1. Это не позволяет смешивать короткие и длинные опции.
Адам Кац
7

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

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

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

Ричард Линч
источник
4
Это встроенные в ksh93 getopts. Помимо этого синтаксиса, он также имеет более сложный синтаксис, который также допускает длинные опции без короткого эквивалента, и многое другое.
jilles
2
Разумный ответ. ОП не указал, что оболочка.
ghoti
6

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

Используя стратегию, предложенную @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» имя аргумента - «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
## /programming/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"
pauljohn32
источник
OPTIND=$(( $OPTIND + 1 )): это необходимо всякий раз, когда вы «проглатываете» параметр OPTIND (например, когда он используется --toc value : значение находится в параметре с номером $ OPTIND. Как только вы получите его для значения toc, вы должны сообщить getopts, что следующий параметр для анализа не является значением, но тот, что после него (следовательно,: OPTIND=$(( $OPTIND + 1 )) . и ваш сценарий (а также сценарий, на который вы ссылаетесь) отсутствуют, после завершения: shift $(( $OPTIND -1 ))(поскольку getopts завершился после анализа параметров 1 в OPTIND-1, вам нужно их сместить так, чтобы $@теперь все оставшиеся «неопционные» параметры
Оливье Дюлак
о, когда вы меняете себя, вы «сдвигаете» параметры под getopts, поэтому OPTIND всегда указывает на правильную вещь ... но я нахожу это очень запутанным. Я считаю (не могу проверить ваш скрипт прямо сейчас), что вам все еще нужно смещение $ (($ OPTIND - 1)) после цикла getopts while, так что $ 1 теперь не указывает на исходный $ 1 (опция), но к первому из оставшихся аргументов (те, которые идут после всех опций и их значений). Например: myrm -foo -bar = baz thisarg thethisone затем еще один
Olivier Dulac
5

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

Эта функция является (надеюсь) 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
Phils
источник
4

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

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

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

Таким образом, я «обновил» bash, встроенный getoptsпутем реализации 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
UrsaDK
источник
3

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

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

#!/bin/bash

# getopt example
# from: /programming/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
kghastie
источник
3

Здесь вы можете найти несколько разных подходов для разбора сложных параметров в 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
user3573558
источник
2

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

алгебра ли
источник
2

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

# 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
jbar
источник
2

Может быть, проще использовать 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
efstathiou_e
источник
+1 - Обратите внимание, что это ограничено ksh93 - из проекта AST с открытым исходным кодом (AT & T Research).
Хенк Лангевельд
2

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

  • короткие bools (-h)
  • короткие опции (-i "image.jpg")
  • long bools (--help)
  • равные опции (--file = "filename.ext")
  • опции пространства (--file "filename.ext")
  • объединенные bools (-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 $*
Хит Даттон
источник
1

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

Я нахожу это очень простым в использовании, вот пример:

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
Orwellophile
источник
1

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

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

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

commonpike
источник
Я не понимаю значение eval здесь
user.friendly
1

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

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
Ашиш Шеткар
источник
0

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, и вам приходится делать большую часть кода проверки ошибок самостоятельно в экземплярах кейса ...

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

estani
источник
Но длинные варианты часто требуют аргументов. И вы могли бы сделать больше с - чтобы это заработало, даже если это что-то вроде хака. В конце концов, можно утверждать, что если он не поддерживает его изначально, то каждый способ его реализации является чем-то вроде хака, но, тем не менее, вы можете расширить - тоже. И да, сдвиг очень полезен, но, конечно, если он ожидает аргумент, он может закончить тем, что следующий аргумент (если он не указан пользователем) является частью ожидаемого аргумента.
Pryftan
да, это документ для длинных аргументов name без аргументов, чтобы отличать их от двух, вам нужна какая-то конфигурация, например, getops. А что касается сдвига, вы всегда можете «положить его обратно» с помощью набора. В любом случае это должно быть настраиваемо, если параметр ожидается или нет. Вы можете даже использовать магию для этого, но тогда вы заставите пользователей использовать - чтобы сигнализировать о прекращении магии и запуске позиционных параметров, что еще хуже, imho.
Эстани
Справедливо. Это более чем разумно. Я даже не помню, о чем шла речь, и я совершенно забыла об этом вопросе. Я только смутно помню, как я нашел это даже. Приветствия. Ох и есть +1 за идею. Вы прошли через усилие, и вы также разъяснили, что вы получили. Я уважаю людей , которые идут через усилие , чтобы дать другим идеи и т.д.
Pryftan
0

Встроенный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 )

Мишель ВОНГВИЛАЙ uxora.com
источник
0

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

wprl
источник
0

EasyOptions обрабатывает короткие и длинные опции:

## Options:
##   --verbose, -v   Verbose mode
##   --logfile=NAME  Log filename

source easyoptions || exit

if test -n "${verbose}"; then
    echo "log file: ${logfile}"
    echo "arguments: ${arguments[@]}"
fi
Ренато Сильва
источник