shell - функцию - bash скрипты примеры




Использование getopts в сценарии оболочки bash для получения длинных и коротких параметров командной строки (20)

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

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

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

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

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


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

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

  • foobar -f --bar
  • foobar --foo -b
  • foobar -bf --bar --foobar
  • foobar -fbFBAshorty --bar -FB -arguments = longhorn
  • foobar -fA "text shorty" -B -arguments = "text longhorn"
  • bash foobar -F --barfoo
  • sh foobar -B - foobar - ...
  • bash ./foobar -F --bar

Некоторые декларации для этого примера

[email protected]
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 these example ######
[email protected]
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

Взгляните на 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 "[email protected]" || 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 .


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

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

aflag=no
bflag=no
flist=""
set -- $(getopt abf: "[email protected]")
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 превосходит, хотя и ограничен тем, что он обрабатывает только однобуквенные варианты.


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

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

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

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

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

while getopts ab:c-: arg; do
  case $arg in
    a )  ARG_A=true ;;
    b )  ARG_B="$OPTARG" ;;
    c )  ARG_C=true ;;
    - )  LONG_OPTARG="${OPTARG#*=}"
         case $OPTARG in
           alpha    )  ARG_A=true ;;
           bravo=?* )  ARG_B="$LONG_OPTARG" ;;
           bravo*   )  echo "No arg for --$OPTARG option" >&2; exit 2 ;;
           charlie  )  ARG_C=true ;;
           alpha* | charlie* )
                       echo "No arg allowed for --$OPTARG option" >&2; exit 2 ;;
           '' )        break ;; # "--" terminates argument processing
           * )         echo "Illegal option --$OPTARG" >&2; exit 2 ;;
         esac ;;
    \? ) exit 2 ;;  # getopts already reported the illegal option
  esac
done
shift $((OPTIND-1)) # remove parsed options and args from [email protected] list

Когда аргумент является тире ( - ), он имеет еще два компонента: имя флага и (необязательно) его аргумент. Я ограничиваю их стандартным способом, каким бы ни была команда, с первым знаком sign ( = ). $LONG_OPTARG - это просто содержимое $OPTARG без имени флага или знака равенства.

Внутренний case реализует длинные варианты вручную, поэтому ему необходимо некоторое обслуживание:

  • bravo=? совпадений --bravo=foo но не --bravo= (примечание: case останавливается после первого совпадения)
  • bravo* следует, отмечая недостающий требуемый аргумент в --bravo и --bravo=
  • alpha* | charlie* alpha* | charlie* ловит аргументы, предоставленные опциям, которые их не поддерживают
  • '' присутствует для поддержки не-параметров, которые начинаются с тире
  • * ловит все остальные длинные параметры и воссоздает ошибку, полученную getopts для недопустимой опции

Вам не обязательно нужны все эти предметы домашнего хозяйства. Например, возможно, вы хотите, чтобы --bravo имел необязательный аргумент (который -b не может поддерживать из-за ограничения в getopts ). Просто удалите =? и связанный с этим случай сбоя, а затем вызывается ${ARG_B:=$DEFAULT_ARG_B} при первом использовании $ARG_B .


Функция 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 filename») быть объединенным с опцией (как в «-ffilename»). В стандарте POSIX говорится, что между ними должно быть пространство, которое в случае «- longoption» прекратит разбор параметров и превратит все longoptions в аргументы без опционов.


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

# A string with command options
[email protected]

# 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 настолько запутывают.


getopt и getopts - разные звери, и люди, похоже, немного недопонимают, что они делают. getopts - это встроенная команда bash для обработки параметров командной строки в цикле и назначение каждой найденной опции и значения, в свою очередь, для встроенных переменных, поэтому вы можете продолжить их обработку. getopt , однако, является внешней утилитой, и на самом деле она не обрабатывает ваши варианты , например, bash getopts , модуль Perl Getopt или модули Python optparse / argparse . Все, что getopt делает, - это canonicalize опций, которые передаются - т.е. преобразовать их в более стандартную форму, так что сценарий оболочки проще обрабатывать. Например, приложение 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' -- "[email protected]"`

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

ПРИМЕЧАНИЕ . На самом деле существуют две совершенно разные версии getopt , basic getopt и GNU getopt с различными функциями и различными соглашениями о вызовах. 2 Базовый 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).


getopts bash getopts не поддерживает длинные имена опций с префиксом с двойной меткой. Он поддерживает только односимвольные параметры.

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

Но реализация BSD getopt (например, в Mac OS X) не поддерживает длинные параметры.

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

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

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

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


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

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


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

./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 за вдохновение ;-)


Может быть, проще использовать 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

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

# 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

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

Используя стратегию, предложенную с помощью @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
## https://.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"

гектометр

не очень удовлетворены чистыми вариантами 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 whatever_you_want_except_spaces --hello 1 2 3

HELLO: 1 LONG_OPT: whatever_you_want_except пространства REUSE: me

1 2 3

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


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.


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

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


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

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

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

#!/bin/bash

# getopt example
# from: https://.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: -- "[email protected]")
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

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

  • короткие bools (-h)
  • короткие варианты (-i "image.jpg")
  • длинные bools (--help)
  • equals options (--file = "filename.ext")
  • space options (--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 $*

#!/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]==> [email protected]"

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






getopts