Використання getopts для обробки довгих та коротких варіантів командного рядка


410

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

Я знаю, що getoptsможна використовувати, але, як і в Perl, я не зміг зробити те ж саме з оболонкою.

Будь-які ідеї, як це можна зробити, щоб я міг використовувати такі варіанти, як:

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

У вищесказаному обидві команди означають одне й те саме для моєї оболонки, але, використовуючи getopts, я не зміг їх реалізувати?


2
ІМХО, прийнята відповідь не найкраща. Це не показує, як використовувати getopts для обробки аргументів "-" і "-", що можна зробити, як показало @Arvid Reffic. Я вставляю іншу відповідь за допомогою подібної концепції, але також стосується помилки користувача у тому, що "забув", щоб вставити значення для необхідних аргументів. Ключовий момент: getopts можна змусити працювати. Користувач повинен уникати використання "getopt" замість того, якщо потрібна мобільність між платформами. Також getopts є частиною стандарту POSIX для снарядів, тому він, ймовірно, є портативним.
pauljohn32

Відповіді:


304

Можна врахувати три варіанти реалізації:

  • Баш вбудований getopts. Це не підтримує імена довгих опцій із префіксом подвійний тире. Він підтримує лише параметри з одним символом.

  • Реалізація окремої getoptкоманди BSD UNIX (для чого використовується 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 з макпортів. У Windows я б встановив GNU getopt разом із Cygwin.
Білл Карвін

2
Мабуть , ksh getopts може обробляти довгі варіанти.
Тгр

1
@Bill +1, хоча побудувати getopt з джерела ( software.frodo.looijaard.name/getopt ) на Mac також досить просто . Ви також можете перевірити версію getopt, встановлену у вашій системі, із сценаріїв "getopt -T; echo $?".
Chinasaur

8
@Bill Karwin: "Вбудований bash getopts не підтримує імена довгих опцій з префіксом подвійного тиру". Але getopts можна зробити для підтримки довгих варіантів: див. Stackoverflow.com/a/7680682/915044 нижче.
TomRoche

305

getoptі getoptsце різні звірі, і люди, здається, мають трохи нерозуміння того, що роблять. getopts- це вбудована команда для bashобробки параметрів командного рядка в циклі та присвоєння кожному знайденому параметру та значенню по черзі вбудованим змінним, тож ви можете їх далі обробляти. getoptоднак це зовнішня утиліта, і вона насправді не обробляє ваші параметри для вас так, як, наприклад, bash getopts, Perl Getoptмодуль або 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 ( getoptза замовчуванням у Linux є GNU . 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, basic getoptта GNU getopt, з різними можливостями та різними умовами виклику. 2 Basic getoptє досить розбитим: він не тільки не обробляє довгі параметри, але навіть не може обробляти вбудовані пробіли всередині аргументів або порожні аргументи, тоді якgetopts робить це правильно. Наведений вище код не буде працювати в базовому getopt. GNU getoptвстановлений за замовчуванням у Linux, але на Mac OS X та FreeBSD його потрібно встановлювати окремо. В Mac OS X встановіть MacPorts ( http://www.macports.org ), а потім зробіть sudo port install getoptінсталяцію GNU getopt(як правило, в /opt/local/bin), і переконайтеся, що /opt/local/binце на шляху вашої оболонки до/usr/bin. На FreeBSD встановіть misc/getopt.

Короткий посібник щодо зміни прикладу коду для вашої власної програми: З перших кількох рядків усі - це "котловарки", які повинні залишатися однаковими, крім лінії, яка дзвонить getopt. Вам слід змінити назву програми після -n, вказати короткі параметри після -oта довгі варіанти після --long. Поставте двокрапку після варіантів, які приймають значення.

Нарешті, якщо ви бачите код, який просто setзамість нього eval set, він був написаний для BSD getopt. Ви повинні змінити його, щоб використовувати eval setстиль, який добре працює з обома версіями getopt, в той час як звичайна setне працює правильно з GNU getopt.

1 Насправді,getopts в ksh93опорах з довгими іменами варіантів, але ця оболонка не використовується так часто , як bash. В zsh, використовувати zparseoptsцю функціональність.

2 Технічно "GNU getopt" - це неправильне значення; ця версія фактично була написана для Linux, а не для проекту GNU. Однак він дотримується всіх конвенцій GNU, і термін "GNU getopt" зазвичай використовується (наприклад, на FreeBSD).


3
Це було дуже корисно, ідея використовувати getopt для перевірки параметрів, а потім обробити ці параметри в дуже простому циклі спрацювала дуже добре, коли я хотів додати довгі параметри стилю до сценарію bash. Дякую.
ianmjones

2
getoptв Linux не є утилітою GNU, і традиційна getoptне спочатку походить з BSD, а з AT&T Unix. ksh93's getopts(також від AT&T) підтримує довгі параметри в стилі GNU.
Стефан Шазелас

@StephaneChazelas - відредаговано так, щоб відобразити ваші коментарі. Я все ще віддаю перевагу терміну "GNU getopt", хоча він є неправильним, оскільки ця версія дотримується конвенцій GNU і, як правило, діє як програма GNU (наприклад, використання POSIXLY_CORRECT), тоді як "getopt з розширеним Linux" помилково говорить про те, що ця версія існує лише на Linux.
Urban Vagabond

1
Він походить від пакета util-linux, тому це лише Linux, оскільки цей пакет програмного забезпечення призначений лише для Linux (який getoptможе легко переноситися на інші Unices, але багато інших програмних засобів, що util-linuxстосуються Linux). Всі програми, що не стосуються GNU, що використовують getopt GNU (3), розуміють $POSIX_CORRECT. Наприклад, ви б не сказали, що aplayце GNU саме з цих причин. Я підозрюю, що коли FreeBSD згадує GNU getopt, вони мають на увазі API GNU getopt (3) C.
Стефан Шазелас

@StephaneChazelas - FreeBSD має повідомлення про помилку "Залежність побудови: Будь ласка, встановіть GNU getopt", яке однозначно посилається на getoptутиліту, а не getopt (3).
Urban Vagabond

202

Функція 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

Після копіювання у ім'я виконуваного файлу = 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 ім'я файлу") бути приєднаним до опції (як у "-файлі"). Стандарт POSIX стверджує, що між ними повинен бути пробіл, який у випадку "- longoption" припинить аналіз параметрів і перетворить усі лонгопсії в аргументи необов'язкових.


2
Одне питання: яка семантика !в val="${!OPTIND}?
TomRoche

2
@TomRoche це непряма заміна
ecbrodie

2
@ecbrodie: Це тому, що насправді було оброблено два аргументи, а не один. Перший аргумент - це слово "loglevel", а наступний - це аргумент. Тим часом, getoptsавтоматично збільшується лише з кроком OPTIND1, але в нашому випадку нам потрібен приріст на 2, тому ми збільшуємо його на 1 вручну, а потім знову getoptsзбільшуємо на 1 автоматично.
Віктор Заманян

3
Оскільки ми тут перебуваємо в рівновазі баша: голі назви змінних дозволені всередині арифметичних виразів, не $потрібно. 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з цитатами (див. Мою відповідь нижче), щоб він також працював правильно з getopt GNU (за замовчуванням в Linux) і правильно обробляє пробіли.
Urban Vagabond

@UrbanVagabond: Я не впевнений, чому я повинен це робити. Питання позначено Unix, а не Linux. Я свідомо показую традиційний механізм, і він має проблеми з пробілами в аргументах і т. Д. Ви можете продемонструвати сучасну версію для Linux, якщо бажаєте, і ваша відповідь це робить. (Зауважу, пасим, що ваше використання ${1+"$@"}вигадливе і суперечить тому, що необхідно в сучасних оболонках, а конкретно з будь-якою оболонкою, яку ви знайдете в Linux. Див. Використання $ 1: + "$ @"} в / bin / sh для обговорення цього позначення.)
Джонатан Леффлер

Ви повинні зробити це, тому що eval setробить все правильно і з GNU, і з BSD getopt, тоді як звичайний setробить тільки правильне з BSD getopt. Тож ви можете також скористатися, eval setщоб заохотити людей вживатися у звичку робити це. До речі, дякую, я не розумів, що більше ${1+"$@"}не потрібен. Мені доводиться писати речі, які працюють як на Mac OS X, так і на Linux - між цими двома вони примушують багато переносимості. Я тільки що перевірив і "$@"дійсно робити правильні речі на всіх sh, bash, kshі zshпід Mac OS X; напевно і під Linux.
Urban 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

1
Ви повинні переписати приклад для використання eval setз цитатами (див. Мою відповідь нижче), щоб він також працював коректно з GNU getopt (за замовчуванням в Linux) і правильно обробляє пробіли.
Urban 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-того самого типу). Потрібно мати можливість вписати їх в аргументи ключових слів, тому що користувачі можуть потім будувати команду поетапно (тобто ls abc -la).
Arne Babenhauserheide

1
@AdamKatz: Я написав невелику статтю з цим: draketo.de/english/free-software/shell-argument-parsing - включає повторне читання решти аргументів, щоб знайти заставні параметри.
Arne Babenhauserheide

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.


2
Це фантастично, але, на жаль, мій getopt (OS X) не підтримує пробілів у аргументах: / цікаво, чи є альтернатива.
Аластер Стюарт

@AlastairStuart - дійсно є альтернатива в OS X. Використовуйте MacPorts для установки GNU getopt (зазвичай він буде встановлений у / opt / local / bin / getopt).
Urban Vagabond

3
@UrbanVagabond - встановлення несистемних інструментів за замовчуванням, на жаль, не є прийнятною вимогою для досить портативного інструменту.
Аластер Стюарт

@AlastairStuart - дивіться мою відповідь на портативне рішення, яке використовує вбудовані getopts, а не GNU getopt. Це те саме, що і базове використання getopts, але з додатковою ітерацією для довгих варіантів.
Адам Кац

31

Використання getoptsз короткими / довгими параметрами та аргументами


Працює з усіма комбінаціями, наприклад:

  • foobar -f --бар
  • foobar --foo -b
  • foobar -bf --bar --foobar
  • foobar -fbFBAshorty --bar -FB - аргументи = longhorn
  • foobar -fA "текст короткий" -B --arguments = "текст longhorn"
  • bash foobar -F --barfoo
  • sh foobar -B --foobar - ...
  • bash ./foobar -F --bar

Деякі заяви для цього прикладу

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

Як виглядатиме функція Usage

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

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
@Theodore Я радий, що це було вам корисно! Це було і для мене болем. Якщо ви зацікавлені, ви можете побачити приклад цього в дії тут: raw.github.com/rafaelrinaldi/swf-to-html/master/swf-to-html.sh

2
Однозначно найпростіший спосіб, який я бачив. Я трохи змінив його, наприклад, використовуючи i = $ (($ i + 1)) замість expr, але концепція є герметичною.
Томас Дігнан

6
Ви зовсім не тупі, але можливо вам не вистачає функції: getopt (s) може розпізнавати змішані варіанти (наприклад: -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 функціональність разом із довгими варіантами, це простий спосіб зробити це.

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


Здається, це працює лише з одним довгим варіантом, але це задовольнило мою потребу. Дякую!
kingjeffrey

У спеціальному випадку --), здається, є shift ;відсутні. На даний момент цей --аргумент залишатиметься першим аргументом без варіанту.
dgw

Я думаю, що це насправді краща відповідь, хоча, як зазначає dgw, --варіант потрібен shiftтам. Я кажу , що це краще , тому що альтернативи або платформи залежні версії getoptабо getopts_longабо ви повинні змусити короткі варіанти , які будуть використовуватися тільки на початку команди (тобто - ви використовуєте getoptsпотім обробляти довгі опції згодом), в той час як це дає якийсь - або замовлення і повний контроль.
Харавік

Ця відповідь змушує мене замислитись, чому у нас є нитка з десятків відповідей, щоб виконати роботу, яку можна виконати не більше, ніж це абсолютно чітке і просте рішення, і якщо є якісь причини для використання мільярдів (-ів) використання випадків, окрім доказування себе.
Флоріан Хейгл

11

Вбудований getoptsне може цього зробити. Існує зовнішня програма getopt (1), яка може це зробити, але ви отримуєте її тільки в Linux з пакету util-linux . Він поставляється із прикладом сценарію getopt-parse.bash .

Існує також getopts_longнаписана як оболонка функція.


3
Він getoptбув включений до FreeBSD версії 1.0 у 1993 році і з того часу є частиною FreeBSD. Таким чином, він був прийнятий від FreeBSD 4.x для включення в проект Дарвіна Apple. Що стосується OS X 10.6.8, сторінка man, включена 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

2
Пояснення було б добре. Перший скрипт приймає короткі параметри лише тоді, коли другий скрипт має помилку під час аналізу аргументів тривалої опції; його змінна повинна бути "${1:0:1}"для аргументу №1, підрядка в індексі 0, довжина 1. Це не дозволяє змішувати короткі та довгі параметри.
Адам Кац

7

У ksh93, getoptsчи підтримує довгі імена ...

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

Або так сказали підручники, які я знайшов. Спробуйте і подивіться.


4
Це вбудований getshts ksh93. Крім цього синтаксису, він також має більш складний синтаксис, який також дозволяє довгі варіанти без короткого еквівалента тощо.
jilles

2
Розумна відповідь. В ОП не вказано, ЩО оболонка.
ghoti

6

Я пишу лише сценарії оболонок час від часу і випадаю з практики, тому будь-який зворотній зв'язок цінується.

Використовуючи стратегію, запропоновану @Arvid Reffic, ми помітили деякі помилки користувача. Користувач, який забуде включити значення, випадково буде вказано ім'я наступного параметра як значення:

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

призведе до того, що значення "loglevel" буде розглядатися як "--toc = ІСТИНА". Цього можна уникнути.

Я адаптував деякі ідеї щодо перевірки помилок користувача для CLI з http://mwiki.wooledge.org/BashFAQ/035 обговорення ручного розбору. Я вставив перевірку помилок при обробці аргументів "-" і "-".

Тоді я почав хитатися з синтаксисом, тому якихось помилки тут є моєю виною, а не оригінальними авторами.

Мій підхід допомагає користувачам, які вважають за краще входити довго із знаком рівності або без нього. Тобто, він повинен мати таку ж відповідь на "--loglevel 9", як "--loglevel = 9". У методі - / space не можна точно знати, чи забуде користувач аргумент, тому потрібне певне здогадування.

  1. якщо користувач має довгий / рівний формат знаків (--opt =), то пробіл після = викликає помилку, оскільки аргумент не наданий.
  2. якщо у користувача аргументи long / space (--opt), цей скрипт викликає збій, якщо не слідує жоден аргумент (кінець команди) або якщо аргумент починається з тире)

Якщо ви починаєте з цього, існує цікава різниця між форматами "--opt = value" та "--opt value". Зі знаком рівності аргумент командного рядка розглядається як "opt = значення", а робота з обробки синтаксичного розбору для розділення на "=". На противагу цьому, зі значенням "--opt" назва аргументу - "opt", і перед нами стоїть завдання отримати наступне значення, подане в командному рядку. Ось де @Arvid Reffic використовував непряме посилання $ {! OPTIND}. Я все ще не розумію цього, ну, взагалі, і коментарі в BashFAQ, схоже, застерігають від цього стилю ( http://mywiki.wooledge.org/BashFAQ/006 ). До речі, я не вважаю, що коментарі попереднього плаката про важливість OPTIND = $ (($ OPTIND + 1)) є правильними. Я хочу сказати,

У новітній версії цього сценарію flag -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

Ось приклад виведення перевірки помилок на користувальницьку інтпу

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

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 thenthisone thenabody
Олів'є Дулак,

5

Винайдено ще одну версію колеса ...

Ця функція є (сподіваємось) сумісною з POSIX сумісною простою бурною оболонкою для отримання GNU. Він підтримує короткі / довгі параметри, які можуть приймати обов'язкові / необов'язкові / без аргументів, а спосіб, у якому вказані параметри, майже ідентичний методу отримання GNU, тому перетворення є тривіальним.

Звичайно, це все-таки значний шматок коду, який слід потрапити в сценарій, але це приблизно половина рядків добре відомої функції оболонки 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

4

Прийнята відповідь робить дуже приємну роботу, вказуючи на всі недоліки вбудованого bash getopts. Відповідь закінчується:

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

І хоча я принципово згоден з цим твердженням, я вважаю, що кількість разів, коли ми всі реалізовували цю функцію в різних сценаріях, виправдовує вкладення трохи зусиль у створення "стандартизованого", добре перевіреного рішення.

Як такий, я "модернізував" баш, вбудований getopts, реалізуючи getopts_longчистий баш, без зовнішніх залежностей. Використання функції на 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

3

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

Я також додав деякі приклади звичаїв та текст HELP. Тут я включу свою трохи розширену версію:

#!/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

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

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

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

+1 - Зауважте, що це обмежено кш93 - від проекту AST з відкритим кодом (AT&T Research).
Хенк Лангевельд

2

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

  • короткі булі (-h)
  • короткі параметри (-i "image.jpg")
  • довгі кипіння (--допомога)
  • дорівнює параметрам (--file = "ім'я файлу.ext")
  • параметри місця (--file "filename.ext")
  • скручені котли (-хвм)

Просто вставте наступне у верхній частині сценарію:

# 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

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 за натхнення ;-)


Я не знаю тут значення 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 буде розглядати його як перший не необов'язковий позиційний параметр і зупинить аналіз будь-яких інших параметрів. У такому випадку вам буде краще обробляти це вручну простою заявою справи.

Це спрацює "завжди":

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

Але це варіант.


Але довгі варіанти часто очікують аргументів. І ви могли б зробити більше з цим - змусити його працювати, хоча це щось злому. Зрештою, можна стверджувати, що якщо він не підтримує його, то кожен спосіб його впровадження є чимось зломним, але, тим не менш, ви можете поширити це - теж. І так, зсув дуже корисний, але, звичайно, якщо він очікує аргументу, це може призвести до того, що наступний аргумент (якщо його не вказано користувачем) є частиною очікуваного аргументу.
Прифтан

так, це пок для назви довгих аргументів без аргументів, для розмежування між двома вам потрібна така конфігурація, як getops. А щодо зміни, ви завжди можете "повернути її назад" з набором. У будь-якому випадку він повинен бути налаштований, якщо параметр очікується чи ні. Ви навіть можете використати для цього якусь магію, але тоді ви змусите користувачів використовувати - сигналізувати про те, що магія припиняється і починаються позиційні параметри, що ще гірше.
estani

Справедливо. Це більш ніж розумно. Тбх, я навіть не пам'ятаю, про що я йшов, і я зовсім забув про це питання. Я лише невиразно пам’ятаю, як я його навіть знайшов. Ура. О, і є +1 для ідеї. Ви пройшли зусилля, а також уточнили, до чого потрапляли. Я поважаю людей, які
намагаються

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 )


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.


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
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.