Как разобрать аргументы командной строки в Bash?

Скажем, у меня есть сценарий, который вызывается с такой строкой:

./myscript -vfd ./foo/bar/someFile -o /fizz/someOtherFile

или этот:

./myscript -v -f -d -o /fizz/someOtherFile ./foo/bar/someFile 

Каким будет принятый способ синтаксического анализа, чтобы в каждом случае (или в некоторой их комбинации) $v, $f и $d были установлены на true, а $outFile был равен /fizz/someOtherFile?

Для пользователей zsh есть отличная встроенная функция под названием zparseopts, которая может: zparseopts -D -E -M -- d=debug -debug=d И иметь как -d, так и --debug в массиве $debug, echo $+debug[1] вернет 0 или 1, если один из них используется. Ссылка: zsh.org/mla/users/2011/msg00350.html

dza 02.08.2016 05:13

Действительно хороший учебник: linuxcommand.org/lc3_wss0120.php. Мне особенно нравится пример «Параметры командной строки».

Gabriel Staples 10.02.2020 21:45

Я создал сценарий, который сделает это за вас, он называется - github.com/unfor19/bargs

Meir Gabay 07.08.2020 17:29

См. Также Предоставить сценарию bash возможность принимать флаги, например, команду? для подробного, специального, длинного и короткого парсера опций. Он не пытается обрабатывать аргументы параметра, прикрепленные к коротким параметрам, или длинные параметры с =, отделяющим имя параметра от значения параметра (в обоих случаях он просто предполагает, что значение параметра находится в следующем аргументе). Он также не обрабатывает кластеризацию коротких опций - вопрос не нужен.

Jonathan Leffler 09.10.2020 18:21
Это отличное руководство от Baeldung shows 4 ways to process command-line arguments in bash, including: 1) positional parameters , , etc., 2) flags with getopts and ${OPTARG}, 3) looping over all parameters ($@), and 4) looping over all parameters using $#, , and the shift operator.
Gabriel Staples 27.12.2020 00:05
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
2 106
5
1 626 336
37
Перейти к ответу Данный вопрос помечен как решенный

Ответы 37

getopt() / getopts() - хороший вариант. Скопировано с здесь:

The simple use of "getopt" is shown in this mini-script:

#!/bin/bash
echo "Before getopt"
for i
do
  echo $i
done
args=`getopt abc:d $*`
set -- $args
echo "After getopt"
for i
do
  echo "-->$i"
done

What we have said is that any of -a, -b, -c or -d will be allowed, but that -c is followed by an argument (the "c:" says that).

If we call this "g" and try it out:

bash-2.05a$ ./g -abc foo
Before getopt
-abc
foo
After getopt
-->-a
-->-b
-->-c
-->foo
-->--

We start with two arguments, and "getopt" breaks apart the options and puts each in its own argument. It also added "--".

Использование $* нарушает использование getopt. (Аргументы заключаются в пробелы.) См. мой ответ для правильного использования.

Robert Siemer 16.04.2016 17:37

Зачем вам усложнять?

SDsolar 10.08.2017 17:07

@Matt J, первая часть скрипта (для i) сможет обрабатывать аргументы с пробелами в них, если вы используете «$ i» вместо $ i. Похоже, что getopts не может обрабатывать аргументы с пробелами. В чем преимущество использования getopt перед циклом for i?

thebunnyrules 01.06.2018 04:57

Я думаю, что это достаточно просто использовать:

#!/bin/bash
#

readopt='getopts $opts opt;rc=$?;[ $rc$opt == 0? ]&&exit 1;[ $rc == 0 ]||{ shift $[OPTIND-1];false; }'

opts=vfdo:

# Enumerating options
while eval $readopt
do
    echo OPT:$opt ${OPTARG+OPTARG:$OPTARG}
done

# Enumerating arguments
for arg
do
    echo ARG:$arg
done

Пример вызова:

./myscript -v -do /fizz/someOtherFile -f ./foo/bar/someFile
OPT:v 
OPT:d 
OPT:o OPTARG:/fizz/someOtherFile
OPT:f 
ARG:./foo/bar/someFile

Я читаю все, и этот - мой любимый. Мне не нравится использовать -a=1 в качестве стиля argc. Я предпочитаю ставить сначала основную опцию -options, а затем специальные с одинарным интервалом -o option. Я ищу самый простой способ читать argvs.

m3nda 21.05.2015 01:50

Он работает очень хорошо, но если вы передадите аргумент параметру, отличному от a:, все следующие параметры будут приняты в качестве аргументов. Вы можете проверить эту строку ./myscript -v -d fail -o /fizz/someOtherFile -f ./foo/bar/someFile своим собственным скриптом. Параметр -d не установлен как d:

m3nda 21.05.2015 02:25

От digitalpeer.com с небольшими изменениями:

Использование myscript.sh -p=my_prefix -s=dirname -l=libname

#!/bin/bash
for i in "$@"
do
case $i in
    -p=*|--prefix=*)
    PREFIX = "${i#*=}"

    ;;
    -s=*|--searchpath=*)
    SEARCHPATH = "${i#*=}"
    ;;
    -l=*|--lib=*)
    DIR = "${i#*=}"
    ;;
    --default)
    DEFAULT=YES
    ;;
    *)
            # unknown option
    ;;
esac
done
echo PREFIX = ${PREFIX}
echo SEARCH PATH = ${SEARCHPATH}
echo DIRS = ${DIR}
echo DEFAULT = ${DEFAULT}

Чтобы лучше понять ${i#*=}, найдите «Удаление подстроки» в это руководство. Функционально он эквивалентен `sed 's/[^=]*=//' <<< "$i"`, который вызывает ненужный подпроцесс, или `echo "$i" | sed 's/[^=]*=//'`, который вызывает ненужные подпроцессы два.

Аккуратный! Хотя это не сработает для аргументов, разделенных пробелами, как mount -t tempfs .... Возможно, это можно исправить с помощью чего-то вроде while [ $# -ge 1 ]; do param=; shift; case $param in; -p) prefix=; shift;; и т. д.

Tobias Kienzler 12.11.2013 16:48

Это не подходит для комбинированных коротких вариантов в стиле -vfd.

Robert Siemer 19.03.2016 18:23
Ответ принят как подходящий

Bash с разделением пробелами (например, --option argument)

cat >/tmp/demo-space-separated.sh <<'EOF'
#!/bin/bash

POSITIONAL=()
while [[ $# -gt 0 ]]
do
key = ""

case $key in
    -e|--extension)
    EXTENSION = ""
    shift # past argument
    shift # past value
    ;;
    -s|--searchpath)
    SEARCHPATH = ""
    shift # past argument
    shift # past value
    ;;
    -l|--lib)
    LIBPATH = ""
    shift # past argument
    shift # past value
    ;;
    --default)
    DEFAULT=YES
    shift # past argument
    ;;
    *)    # unknown option
    POSITIONAL+=("") # save it in an array for later
    shift # past argument
    ;;
esac
done
set -- "${POSITIONAL[@]}" # restore positional parameters

echo "FILE EXTENSION  = ${EXTENSION}"
echo "SEARCH PATH     = ${SEARCHPATH}"
echo "LIBRARY PATH    = ${LIBPATH}"
echo "DEFAULT         = ${DEFAULT}"
echo "Number files in SEARCH PATH with EXTENSION:" $(ls -1 "${SEARCHPATH}"/*."${EXTENSION}" | wc -l)
if [[ -n  ]]; then
    echo "Last line of file specified as non-opt/last argument:"
    tail -1 ""
fi
EOF

chmod +x /tmp/demo-space-separated.sh

/tmp/demo-space-separated.sh -e conf -s /etc -l /usr/lib /etc/hosts

Результат копирования-вставки блока выше:

FILE EXTENSION  = conf
SEARCH PATH     = /etc
LIBRARY PATH    = /usr/lib
DEFAULT         =
Number files in SEARCH PATH with EXTENSION: 14
Last line of file specified as non-opt/last argument:
#93.184.216.34    example.com

Использование:

demo-space-separated.sh -e conf -s /etc -l /usr/lib /etc/hosts


Bash с разделением на равенство (например, --option=argument)

cat >/tmp/demo-equals-separated.sh <<'EOF'
#!/bin/bash

for i in "$@"
do
case $i in
    -e=*|--extension=*)
    EXTENSION = "${i#*=}"
    shift # past argument=value
    ;;
    -s=*|--searchpath=*)
    SEARCHPATH = "${i#*=}"
    shift # past argument=value
    ;;
    -l=*|--lib=*)
    LIBPATH = "${i#*=}"
    shift # past argument=value
    ;;
    --default)
    DEFAULT=YES
    shift # past argument with no value
    ;;
    *)
          # unknown option
    ;;
esac
done
echo "FILE EXTENSION  = ${EXTENSION}"
echo "SEARCH PATH     = ${SEARCHPATH}"
echo "LIBRARY PATH    = ${LIBPATH}"
echo "DEFAULT         = ${DEFAULT}"
echo "Number files in SEARCH PATH with EXTENSION:" $(ls -1 "${SEARCHPATH}"/*."${EXTENSION}" | wc -l)
if [[ -n  ]]; then
    echo "Last line of file specified as non-opt/last argument:"
    tail -1 
fi
EOF

chmod +x /tmp/demo-equals-separated.sh

/tmp/demo-equals-separated.sh -e=conf -s=/etc -l=/usr/lib /etc/hosts

Результат копирования-вставки блока выше:

FILE EXTENSION  = conf
SEARCH PATH     = /etc
LIBRARY PATH    = /usr/lib
DEFAULT         =
Number files in SEARCH PATH with EXTENSION: 14
Last line of file specified as non-opt/last argument:
#93.184.216.34    example.com

Использование:

demo-equals-separated.sh -e=conf -s=/etc -l=/usr/lib /etc/hosts


Чтобы лучше понять ${i#*=}, найдите «Удаление подстроки» в это руководство. Функционально он эквивалентен `sed 's/[^=]*=//' <<< "$i"`, который вызывает ненужный подпроцесс, или `echo "$i" | sed 's/[^=]*=//'`, который вызывает ненужные подпроцессы два.


Использование bash с getopt [s]

getopt (1) ограничения (более старые, относительно недавние версии getopt):

  • не может обрабатывать аргументы, которые являются пустыми строками
  • не может обрабатывать аргументы со встроенными пробелами

Более поздние версии getopt не имеют этих ограничений.

От: http://mywiki.wooledge.org/BashFAQ/035#getopts


Кроме того, оболочка POSIX (и другие) предлагает getopts, который не имеет этих ограничений. Я включил упрощенный пример getopts.

cat >/tmp/demo-getopts.sh <<'EOF'
#!/bin/sh

# A POSIX variable
OPTIND=1         # Reset in case getopts has been used previously in the shell.

# Initialize our own variables:
output_file = ""
verbose=0

while getopts "h?vf:" opt; do
    case "$opt" in
    h|\?)
        show_help
        exit 0
        ;;
    v)  verbose=1
        ;;
    f)  output_file=$OPTARG
        ;;
    esac
done

shift $((OPTIND-1))

[ "${1:-}" = "--" ] && shift

echo "verbose=$verbose, output_file='$output_file', Leftovers: $@"
EOF

chmod +x /tmp/demo-getopts.sh

/tmp/demo-getopts.sh -vf /etc/hosts foo bar

Результат копирования-вставки блока выше:

verbose=1, output_file='/etc/hosts', Leftovers: foo bar

Использование:

demo-getopts.sh -vf /etc/hosts foo bar

Преимущества getopts:

  1. Он более портативный и будет работать с другими оболочками, такими как dash.
  2. Он может автоматически обрабатывать несколько отдельных опций, таких как -vf filename, типичным для Unix способом.

Недостатком getopts является то, что он может обрабатывать только короткие варианты (-h, но не --help) без дополнительного кода.

Существует руководство по getopts, который объясняет, что означают весь синтаксис и переменные. В bash также есть help getopts, который может быть информативным.

Это правда? Согласно Википедия, существует более новая расширенная версия getopt GNU, которая включает в себя все функции getopts, а затем некоторые. man getopt в Ubuntu 13.04 выводит getopt - parse command options (enhanced) в качестве имени, поэтому я предполагаю, что эта расширенная версия сейчас является стандартной.

Livven 07.06.2013 01:19

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

szablica 17.07.2013 19:23

@Livven, getopt не является утилитой GNU, это часть util-linux.

Stephane Chazelas 20.08.2014 23:55

Я использовал это с небольшой модификацией, чтобы добавить все нераспознанные параметры в массив, который затем становится нашим новым $ @ gist.github.com/rciorba/514fd75f4a6471d44d71

radu.ciorba 11.11.2015 12:33

Если вы используете -gt 0, удалите shift после esac, увеличьте все shift на 1 и добавьте этот случай: *) break;;, вы можете обрабатывать необязательные аргументы. Пример: pastebin.com/6DJ57HTc

Nicolas Lacombe 20.06.2016 00:22
ворчать re: using all-uppercase variable names, in contravention of Соглашение POSIX specifying that upper-case variables are used for names with meaning to POSIX-specified tools, and lower-case names are reserved for application use. (This is relevant to shell variables as well, as a shell variable assignment will overwrite any like-named environment variable that exists).
Charles Duffy 12.10.2016 00:49

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

gcscaglia 03.03.2017 18:18

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

rjurney 10.06.2017 01:29

Вы не повторяете –default. В первом примере я заметил, что если –default является последним аргументом, он не обрабатывается (считается неопциональным), если while [[ $# -gt 1 ]] не установлен как while [[ $# -gt 0 ]].

kolydart 10.07.2017 11:11

Я наконец исправил проблему в примере «Straight Bash Space Separated» с -gt 0 и -gt 1, как описано @ NicolasMongrain-Lacombe и @kolydart

Bruno Bronosky 23.09.2017 08:13

getopts "h?vf:" должен быть getopts "hvf:" без вопросительного знака. Непризнанные аргументы сохраняются как ? в $opt. Цитата из man builtins: “The colon and question mark characters may not be used as option characters.”

Simon A. Eugster 06.10.2017 10:04

Если вы работаете в «строгом режиме» bash (#!/bin/bash -u или set -eu), восстанавливайте позиционные параметры только в том случае, если массив POSITIONAL не пуст, чтобы избежать ошибки unbound variable, например: if [ ${#POSITIONAL[@]} -gt 0 ]; then set -- "${POSITIONAL[@]}" ; fi # restore positional parameters

Jakub Kukul 22.01.2018 17:50

Первый пример, похоже, не обрабатывает позиционные аргументы с пробелами: test.sh "a b" будет иметь , установленный на «a», а , установленный на «b».

lionello 19.02.2018 09:45

Как я могу обработать часть аргументов, распознаваемых текущим скриптом, и передать остальное другому скрипту с помощью $@? $@ пуст после анализа аргументов, как показано в ответе.

Martynas Jusevičius 22.08.2018 15:38

@ MartynasJusevičius команды shift принимают их или $@, поэтому ограничьте количество смен.

Bruno Bronosky 22.08.2018 15:41

Значит, мне нужно было бы собирать неизвестные параметры в свой собственный массив вместо $@? Я думал, что $POSITIONAL делает что-то подобное, но похоже, что он хранит только ключи, а не значения. Какая у этого цель?

Martynas Jusevičius 22.08.2018 15:49

Собственно, если синтаксис +=("") добавить к массиву, у меня это не сработает. Когда я печатаю $POSITIONAL, я получаю только самый первый вариант. Edit: nevermind, ${POSITIONAL[@]} работает :) Спасибо.

Martynas Jusevičius 22.08.2018 16:05

при использовании «сдвига» в цикле я бы посоветовал сделать что-то вроде: shift || exit 1 Это потому, что, если сдвиг работает неправильно и по какой-то причине не удается сдвинуть аргументы, вы можете попасть в бесконечный цикл. Таким образом, вместо этого, если у shift есть возвращаемое значение, отличное от 0, вы можете безопасно выйти и сообщить об ошибке.

pappix 22.11.2018 17:55

Bash Equals-Separated (e.g., --option=argument) (without getopt[s]), какие изменения мне нужно будет сделать для -x y вместо -x=y?

Manish Kumar 25.01.2019 15:13

Это очень хороший способ сделать это (без getopts), если вы знаете, что можете использовать bash. Для меня это почти 99% времени. Я использую это с большинством моих скриптов среднего уровня в моей собственной среде или как вспомогательные скрипты в моем собственном программном обеспечении.

Harlin 20.04.2019 02:23

В методе Bash Equals-Separated (e.g., --option=argument) (without getopt[s]) зачем нужен встроенный shift? Разве цикл for не должен автоматически перебирать каждый параметр? Если вы использовали предоставленный пример как есть, но просто удалили команды shift, не было бы лучше, если бы вы не отбрасывали свои входные данные?

Tyrel Kostyk 27.06.2019 02:38

@TyrelKostyk, если вы действительно попробуете свое предложение, вы увидите, что сдвиг - это то, что заставляет указывать на первый неопциональный аргумент /etc/hosts. Если вы не переключитесь, вам придется увеличить свой собственный ((counter++)) в цикле, а затем вызвать ${!counter}, чтобы получить «переменную переменную». Если тебе так лучше, сделай это. Но я думаю, что это ненужная сложность, чтобы избежать отбрасывание входов, которые хранятся как именованные переменные.

Bruno Bronosky 27.06.2019 14:34

@BrunoBronosky Спасибо за ответ! Имеет смысл. Я обнаружил кое-что из своего замешательства: я применял ваш ответ в форме функции "parse_inputs". И я обнаружил, что при использовании вашей точной реализации и вызове parse_inputs "$@" оператор shift не имеет никакого эффекта, и независимо от того, используете вы его или нет, он не отбрасывает ваши входные данные. (Следует ли добавить это в свой пост в качестве редактирования?)

Tyrel Kostyk 27.06.2019 19:49

Я использую ваш первый метод, и он не работает, если в значении есть пробелы

Freedo 29.01.2020 11:34

@TyrelKostyk ваша проблема вызвана тем, что функция получает собственный массив аргументов. Поэтому, когда вы используете shift внутри функции, он удаляет элементы из массива аргументов функция, а не из всего скрипта. Решением может быть создание массива positional в вашей функции, а затем использование set -- ${positional[@]} сразу после вызова функции синтаксического анализа.

lxop 21.02.2020 06:03

Я проголосовал против, потому что считаю, что приведенный ниже ответ является правильным.

chenrici 07.04.2020 21:32

что делает эта линия cat >/tmp/demo-getopts.sh <<'EOF'

simplename 30.09.2020 21:12

Что означает это выражение в bash, разделенном пробелами: POSITIONAL = () ??

Jaraws 15.11.2020 16:59

Используйте модуль "arguments" из bash-модули

Пример:

#!/bin/bash
. import.sh log arguments

NAME = "world"

parse_arguments "-n|--name)NAME;S" -- "$@" || {
  error "Cannot parse command line."
  exit 1
}

info "Hello, $NAME!"

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

function waitForWeb () {
   local OPTIND=1 OPTARG OPTION
   local host=localhost port=8080 proto=http
   while getopts "h:p:r:" OPTION; do
      case "$OPTION" in
      h)
         host = "$OPTARG"
         ;;
      p)
         port = "$OPTARG"
         ;;
      r)
         proto = "$OPTARG"
         ;;
      esac
   done
...
}

Если вы создаете сценарии, которые взаимозаменяемы с другими утилитами, может быть полезна гибкость, указанная ниже.

Либо:

command -x=myfilename.ext --another_switch 

Или же:

command -x myfilename.ext --another_switch

Вот код:

STD_IN=0

prefix = ""
key = ""
value = ""
for keyValue in "$@"
do
  case "${prefix}${keyValue}" in
    -i=*|--input_filename=*)  key = "-i";     value = "${keyValue#*=}";; 
    -ss=*|--seek_from=*)      key = "-ss";    value = "${keyValue#*=}";;
    -t=*|--play_seconds=*)    key = "-t";     value = "${keyValue#*=}";;
    -|--stdin)                key = "-";      value=1;;
    *)                                      value=$keyValue;;
  esac
  case $key in
    -i) MOVIE=$(resolveMovie "${value}");  prefix = ""; key = "";;
    -ss) SEEK_FROM = "${value}";          prefix = ""; key = "";;
    -t)  PLAY_SECONDS = "${value}";           prefix = ""; key = "";;
    -)   STD_IN=${value};                   prefix = ""; key = "";; 
    *)   prefix = "${keyValue} = ";;
  esac
done

Это также может быть полезно знать, вы можете установить значение и, если кто-то предоставит ввод, заменить значение по умолчанию этим значением.

myscript.sh -f ./serverlist.txt или просто ./myscript.sh (и принимает значения по умолчанию)

    #!/bin/bash
    # --- set the value, if there is inputs, override the defaults.

    HOME_FOLDER = "${HOME}/owned_id_checker"
    SERVER_FILE_LIST = "${HOME_FOLDER}/server_list.txt"

    while [[ $# > 1 ]]
    do
    key = ""
    shift

    case $key in
        -i|--inputlist)
        SERVER_FILE_LIST = ""
        shift
        ;;
    esac
    done


    echo "SERVER LIST   = ${SERVER_FILE_LIST}"

Я использовал предыдущие ответы в качестве отправной точки, чтобы привести в порядок мой старый разбор параметров adhoc. Затем я переработал следующий код шаблона. Он обрабатывает как длинные, так и короткие параметры, используя = или аргументы, разделенные пробелами, а также несколько коротких параметров, сгруппированных вместе. Наконец, он повторно вставляет все непараметрические аргументы обратно в переменные $ 1, $ 2 ...

#!/usr/bin/env bash

# NOTICE: Uncomment if your script depends on bashisms.
#if [ -z "$BASH_VERSION" ]; then bash $0 $@ ; exit $? ; fi

echo "Before"
for i ; do echo - $i ; done


# Code template for parsing command line parameters using only portable shell
# code, while handling both long and short params, handling '-f file' and
# '-f=file' style param data and also capturing non-parameters to be inserted
# back into the shell positional parameters.

while [ -n "$1" ]; do
        # Copy so we can modify it (can't modify $1)
        OPT = "$1"
        # Detect argument termination
        if [ x"$OPT" = x"--" ]; then
                shift
                for OPT ; do
                        REMAINS = "$REMAINS \"$OPT\""
                done
                break
        fi
        # Parse current opt
        while [ x"$OPT" != x"-" ] ; do
                case "$OPT" in
                        # Handle --flag=value opts like this
                        -c=* | --config=* )
                                CONFIGFILE = "${OPT#*=}"
                                shift
                                ;;
                        # and --flag value opts like this
                        -c* | --config )
                                CONFIGFILE = "$2"
                                shift
                                ;;
                        -f* | --force )
                                FORCE=true
                                ;;
                        -r* | --retry )
                                RETRY=true
                                ;;
                        # Anything unknown is recorded for later
                        * )
                                REMAINS = "$REMAINS \"$OPT\""
                                break
                                ;;
                esac
                # Check for multiple short options
                # NOTICE: be sure to update this pattern to match valid options
                NEXTOPT = "${OPT#-[cfr]}" # try removing single short opt
                if [ x"$OPT" != x"$NEXTOPT" ] ; then
                        OPT = "-$NEXTOPT"  # multiple short opts, keep going
                else
                        break  # long form, exit inner loop
                fi
        done
        # Done with that param. move to next
        shift
done
# Set the non-parameters back into the positional parameters ($1 $2 ..)
eval set -- $REMAINS


echo -e "After: \n configfile='$CONFIGFILE' \n force='$FORCE' \n retry='$RETRY' \n remains='$REMAINS'"
for i ; do echo - $i ; done

Этот код не может обрабатывать параметры с такими аргументами: -c1. И использование = для отделения коротких опций от их аргументов необычно ...

Robert Siemer 06.12.2015 16:47

Я столкнулся с двумя проблемами с этим полезным фрагментом кода: 1) «shift» в случае «-c = foo» заканчивается тем, что съедает следующий параметр; и 2) 'c' не следует включать в шаблон «[cfr]» для комбинируемых коротких опций.

sfnd 06.06.2016 22:28

getopts отлично работает, если # 1 он у вас установлен, а # 2 вы собираетесь запускать на той же платформе. OSX и Linux (например) ведут себя по-разному в этом отношении.

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

./script --arg1=value1 --arg2 value2 --shouldClean

# parse the arguments.
COUNTER=0
ARGS=("$@")
while [ $COUNTER -lt $# ]
do
    arg=${ARGS[$COUNTER]}
    let COUNTER=COUNTER+1
    nextArg=${ARGS[$COUNTER]}

    if [[ $skipNext -eq 1 ]]; then
        echo "Skipping"
        skipNext=0
        continue
    fi

    argKey = ""
    argVal = ""
    if [[ "$arg" =~ ^\- ]]; then
        # if the format is: -key=value
        if [[ "$arg" =~ \= ]]; then
            argVal=$(echo "$arg" | cut -d'=' -f2)
            argKey=$(echo "$arg" | cut -d'=' -f1)
            skipNext=0

        # if the format is: -key value
        elif [[ ! "$nextArg" =~ ^\- ]]; then
            argKey = "$arg"
            argVal = "$nextArg"
            skipNext=1

        # if the format is: -key (a boolean flag)
        elif [[ "$nextArg" =~ ^\- ]] || [[ -z "$nextArg" ]]; then
            argKey = "$arg"
            argVal = ""
            skipNext=0
        fi
    # if the format has not flag, just a value.
    else
        argKey = ""
        argVal = "$arg"
        skipNext=0
    fi

    case "$argKey" in 
        --source-scmurl)
            SOURCE_URL = "$argVal"
        ;;
        --dest-scmurl)
            DEST_URL = "$argVal"
        ;;
        --version-num)
            VERSION_NUM = "$argVal"
        ;;
        -c|--clean)
            CLEAN_BEFORE_START = "1"
        ;;
        -h|--help|-help|--h)
            showUsage
            exit
        ;;
    esac
done

Нет ответа, демонстрирует улучшенный getopt. И получивший наибольшее количество голосов ответ вводит в заблуждение: Он либо игнорирует короткие опции стиля -⁠vfd (запрошенные OP), либо опции после позиционных аргументов (также запрошенные OP); и игнорирует ошибки синтаксического анализа. Вместо:

  • Используйте улучшенный getopt от util-linux или ранее GNU glibc.1
  • Он работает с getopt_long() функцией C GNU glibc.
  • никакое другое решение на этой странице не может сделать все это:
    • обрабатывает пробелы, символы кавычек и даже двоичные данные в аргументах 2 (не расширенный getopt не может этого сделать)
    • он может обрабатывать параметры в конце: script.sh -o outFile file1 file2 -v (getopts этого не делает)
    • допускает длинные параметры в стиле =: script.sh --outfile=fileOut --infile fileIn (разрешая оба варианта длинны, если выполняется самостоятельный анализ)
    • позволяет комбинировать короткие варианты, например -vfd (реальная работа при самостоятельном разборе)
    • позволяет касаться аргументов-опций, например -oOutfile или -vfdoOutfile
  • Уже настолько старый 3, что ни в одной системе GNU этого нет (например, он есть в любом Linux).
  • Вы можете проверить его наличие с помощью: getopt --test → return value 4.
  • Другие getopt или встроенные в оболочку getopts имеют ограниченное использование.

Следующие звонки

myscript -vfd ./foo/bar/someFile -o /fizz/someOtherFile
myscript -v -f -d -o/fizz/someOtherFile -- ./foo/bar/someFile
myscript --verbose --force --debug ./foo/bar/someFile -o/fizz/someOtherFile
myscript --output=/fizz/someOtherFile ./foo/bar/someFile -vfd
myscript ./foo/bar/someFile -df -v --output /fizz/someOtherFile

все возвращаются

verbose: y, force: y, debug: y, in: ./foo/bar/someFile, out: /fizz/someOtherFile

со следующим myscript

#!/bin/bash
# More safety, by turning some bugs into errors.
# Without `errexit` you don’t need ! and can replace
# PIPESTATUS with a simple $?, but I don’t do that.
set -o errexit -o pipefail -o noclobber -o nounset

# -allow a command to fail with !’s side effect on errexit
# -use return value from ${PIPESTATUS[0]}, because ! hosed $?
! getopt --test > /dev/null 
if [[ ${PIPESTATUS[0]} -ne 4 ]]; then
    echo 'I’m sorry, `getopt --test` failed in this environment.'
    exit 1
fi

OPTIONS=dfo:v
LONGOPTS=debug,force,output:,verbose

# -regarding ! and PIPESTATUS see above
# -temporarily store output to be able to check for errors
# -activate quoting/enhanced mode (e.g. by writing out “--options”)
# -pass arguments only via   -- "$@"   to separate them correctly
! PARSED=$(getopt --options=$OPTIONS --longoptions=$LONGOPTS --name "$0" -- "$@")
if [[ ${PIPESTATUS[0]} -ne 0 ]]; then
    # e.g. return value is 1
    #  then getopt has complained about wrong arguments to stdout
    exit 2
fi
# read getopt’s output this way to handle the quoting right:
eval set -- "$PARSED"

d=n f=n v=n outFile=-
# now enjoy the options in order and nicely split until we see --
while true; do
    case "$1" in
        -d|--debug)
            d=y
            shift
            ;;
        -f|--force)
            f=y
            shift
            ;;
        -v|--verbose)
            v=y
            shift
            ;;
        -o|--output)
            outFile = "$2"
            shift 2
            ;;
        --)
            shift
            break
            ;;
        *)
            echo "Programming error"
            exit 3
            ;;
    esac
done

# handle non-option arguments
if [[ $# -ne 1 ]]; then
    echo "$0: A single input file is required."
    exit 4
fi

echo "verbose: $v, force: $f, debug: $d, in: $1, out: $outFile"

Усовершенствованный getopt 1 доступен в большинстве «bash-систем», включая Cygwin; в OS X попробуйте варить установить gnu-getopt или sudo port install getopt
2 соглашения POSIX exec() не имеют надежного способа передать двоичный NULL в аргументах командной строки; эти байты преждевременно завершают аргумент
Первая версия 3, выпущенная в 1997 году или раньше (я отслеживал ее только до 1997 года)

Спасибо за это. Только что подтверждено из таблицы функций на en.wikipedia.org/wiki/Getopts, если вам нужна поддержка длинных опций, и вы не используете Solaris, getopt - это то, что вам нужно.

johncip 12.01.2017 05:00

Я считаю, что единственное предостережение относительно getopt заключается в том, что его нельзя использовать удобно в сценариях оболочки, где у вас может быть несколько параметров, специфичных для сценария оболочки, а затем передать параметры сценария, не являющиеся оболочкой, в оболочку исполняемого файла без изменений. Допустим, у меня есть оболочка grep под названием mygrep, и у меня есть опция --foo, специфичная для mygrep, тогда я не могу использовать mygrep --foo -A 2, и -A 2 автоматически передается на grep; Мне необходимость делать mygrep --foo -- -A 2. Вот моя реализация поверх вашего решения.

Kaushal Modi 27.04.2017 17:02

Мне нравится этот ответ. Я считаю, что утверждение с сайта wooledge.org о том, что использование расширенного getopt означает, что вы будете выполнять вдвое больше работы, вводит в заблуждение. Большинство сценариев я пишу для себя. Они должны работать в моей системе. Я никогда никому их не передаю (кроме, может быть, других на работе, если это такой сценарий). В лучшем случае мне нужно протестировать, чтобы убедиться, что установлен расширенный getopt, а затем выйти с сообщением «Для запуска этого сценария требуется расширенный getopt». Очень просто. Тем не менее, я не уверен, что когда-либо видел, чтобы расширенный getopt устанавливался по умолчанию. Это часть linux-util, который входит в состав пакета разработки для большинства дистрибутивов.

bobpaul 20.03.2018 19:22

@bobpaul Ваше утверждение об util-linux неверно и также вводит в заблуждение: пакет отмечен как «необходимый» в Ubuntu / Debian. Таким образом, он всегда установлен. - О каких дистрибутивах вы говорите (где вы говорите, что их нужно установить специально)?

Robert Siemer 21.03.2018 12:16

Я не знаю сроков разных ответов, но главный ответ делает охватывает короткие варианты стиля -vfd посредством встроенного getopts.

Benjamin W. 23.07.2018 18:15

Обратите внимание, что это не работает на Mac по крайней мере до текущей версии 10.14.3. Доставленный товар - это BSD выпуска 1999 года ...

jjj 10.04.2019 16:12

@jjj сноска 1 касается OS X. - Для готового решения OS X проверьте другие вопросы и ответы. Если честно: для реального программирования не используйте bash. ;-)

Robert Siemer 11.04.2019 05:24

@BenjaminW. Ответ, получивший наибольшее количество голосов, охватывает два решения для самостоятельного анализа и getopts. Первые не объединяют короткие варианты, а вторые не разбирают варианты после аргументов, не являющихся опциями.

Robert Siemer 15.05.2019 14:49

Есть ли ссылка на документ по команде getopt? Все, что я вижу после поиска в Google getopt, касается функции c, а не команды терминала.

Jay Somedon 14.07.2019 19:34

@JaySomedon man getopt работает в командной строке и неплохо работает с Google. Перейдите к разделу 1, то есть к getopt (1), потому что getopt (3) - это функция C, о которой вы говорите.

Robert Siemer 20.08.2019 02:35

Что означает главный восклицательный знак? Может ли кто-нибудь добавить ссылку?

transang 28.11.2019 15:32

@transang Логическое отрицание возвращаемого значения. И его побочный эффект: разрешить выполнение команды (иначе errexit прервет выполнение программы в случае ошибки). - Комментарии в сценарии расскажут больше. В противном случае: man bash

Robert Siemer 29.11.2019 05:04

Я думаю, вам следует изменить свой ответ, чтобы не использовать set и работать с настройками по умолчанию. Это смешивание слишком большого количества вещей в одной посуде. ИМХО.

einpoklum 10.10.2020 21:25

@einpoklum Есть два set, и я думаю, вы имеете в виду первое: я не согласен. Я понимаю вашу точку зрения, но я не хочу давать ответ, который не работает в среде, которую я считаю «наилучшей практикой».

Robert Siemer 11.10.2020 13:11

Смешивание позиционных и флаговых аргументов

--param = arg (ограничено равно)

Свободное смешивание флагов между позиционными аргументами:

./script.sh dumbo 127.0.0.1 --environment=production -q -d
./script.sh dumbo --environment=production 127.0.0.1 --quiet -d

можно сделать с помощью довольно краткого подхода:

# process flags
pointer=1
while [[ $pointer -le $# ]]; do
   param=${!pointer}
   if [[ $param != "-"* ]]; then ((pointer++)) # not a parameter flag so advance pointer
   else
      case $param in
         # paramter-flags with arguments
         -e=*|--environment=*) environment = "${param#*=}";;
                  --another=*) another = "${param#*=}";;

         # binary flags
         -q|--quiet) quiet=true;;
                 -d) debug=true;;
      esac

      # splice out pointer frame from positional list
      [[ $pointer -gt 1 ]] \
         && set -- ${@:1:((pointer - 1))} ${@:((pointer + 1)):$#} \
         || set -- ${@:((pointer + 1)):$#};
   fi
done

# positional remain
node_name=
ip_address=

--param arg (через пробел)

Обычно проще не смешивать стили --flag=value и --flag value.

./script.sh dumbo 127.0.0.1 --environment production -q -d

Это немного рискованно для чтения, но все еще актуально

./script.sh dumbo --environment production 127.0.0.1 --quiet -d

Источник

# process flags
pointer=1
while [[ $pointer -le $# ]]; do
   if [[ ${!pointer} != "-"* ]]; then ((pointer++)) # not a parameter flag so advance pointer
   else
      param=${!pointer}
      ((pointer_plus = pointer + 1))
      slice_len=1

      case $param in
         # paramter-flags with arguments
         -e|--environment) environment=${!pointer_plus}; ((slice_len++));;
                --another) another=${!pointer_plus}; ((slice_len++));;

         # binary flags
         -q|--quiet) quiet=true;;
                 -d) debug=true;;
      esac

      # splice out pointer frame from positional list
      [[ $pointer -gt 1 ]] \
         && set -- ${@:1:((pointer - 1))} ${@:((pointer + $slice_len)):$#} \
         || set -- ${@:((pointer + $slice_len)):$#};
   fi
done

# positional remain
node_name=
ip_address=

Предлагаю свой вариант парсинга опций, который позволяет:

-s p1
--stage p1
-w somefolder
--workfolder somefolder
-sw p1 somefolder
-e=hello

Также позволяет это (может быть нежелательным):

-s--workfolder p1 somefolder
-se=hello p1
-swe=hello p1 somefolder

Перед использованием вы должны решить, следует ли использовать = для опции или нет. Это сделано для того, чтобы код оставался чистым (иш).

while [[ $# > 0 ]]
do
    key = ""
    while [[ ${key+x} ]]
    do
        case $key in
            -s*|--stage)
                STAGE = ""
                shift # option has parameter
                ;;
            -w*|--workfolder)
                workfolder = ""
                shift # option has parameter
                ;;
            -e=*)
                EXAMPLE = "${key#*=}"
                break # option has been fully handled
                ;;
            *)
                # unknown option
                echo Unknown option: $key #1>&2
                exit 10 # either this: my preferred way to handle unknown options
                break # or this: do this to signal the option has been handled (if exit isn't used)
                ;;
        esac
        # prepare for next option in this key, if any
        [[ "$key" = -? || "$key" == --* ]] && unset key || key = "${key/#-?/-}"
    done
    shift # option(s) fully processed, proceed to next input argument
done

что означает "+ x" на $ {key + x}?

Luca Davanzo 14.11.2016 20:56

Это тест, чтобы увидеть, присутствует ли «ключ» или нет. Далее я сбрасываю ключ, и это прерывает внутренний цикл while.

galmok 15.11.2016 12:10

while [ "$#" -gt 0 ]; do
  case "" in
    -n) name = ""; shift 2;;
    -p) pidfile = ""; shift 2;;
    -l) logfile = ""; shift 2;;

    --name=*) name = "${1#*=}"; shift 1;;
    --pidfile=*) pidfile = "${1#*=}"; shift 1;;
    --logfile=*) logfile = "${1#*=}"; shift 1;;
    --name|--pidfile|--logfile) echo " requires an argument" >&2; exit 1;;
    
    -*) echo "unknown option: " >&2; exit 1;;
    *) handle_argument ""; shift 1;;
  esac
done

Это решение:

  • обрабатывает -n arg и --name=arg
  • позволяет аргументы в конце
  • показывает нормальные ошибки, если что-то написано с ошибками
  • совместим, не использует башизмов
  • читаемый, не требует поддержания состояния в цикле

Извините за задержку. В моем сценарии функция handle_argument получает все аргументы, не являющиеся параметрами. Вы можете заменить эту строку чем угодно, например *) die "unrecognized argument: ", или собрать аргументы в переменную *) args+ = ""; shift 1;;.

bronson 08.10.2015 23:41

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

Guilherme Garnier 13.04.2018 19:10

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

lauksas 28.04.2019 02:22

Я внес правку (она ожидает рассмотрения), чтобы добавить несколько полезных функций, сохранив при этом простой и небольшой код. Для более интересных функций, таких как несколько вариантов с одной буквой в одном аргументе, вам следует попробовать getopt или getopts.

leogama 27.06.2020 04:28

Вот мое улучшенное решение ответа Бруно Броноски с использованием переменных массивов.

он позволяет вам смешивать позиции параметров и давать вам массив параметров, сохраняя порядок без опций

#!/bin/bash

echo $@

PARAMS=()
SOFT=0
SKIP=()
for i in "$@"
do
case $i in
    -n=*|--skip=*)
    SKIP+=("${i#*=}")
    ;;
    -s|--soft)
    SOFT=1
    ;;
    *)
        # unknown option
        PARAMS+=("$i")
    ;;
esac
done
echo "SKIP            = ${SKIP[@]}"
echo "SOFT            = $SOFT"
    echo "Parameters:"
    echo ${PARAMS[@]}

Выведет, например:

$ ./test.sh parameter -s somefile --skip=.c --skip=.obj
parameter -s somefile --skip=.c --skip=.obj
SKIP            = .c .obj
SOFT            = 1
Parameters:
parameter somefile

Вы используете сдвиг для известных аргументов, а не для неизвестных, поэтому ваш оставшийся $@ будет состоять только из первых двух аргументов (в том порядке, в котором они передаются), что может привести к некоторым ошибкам, если вы попытаетесь использовать $@ позже. Вам не нужен сдвиг для параметров =, поскольку вы не обрабатываете пробелы и получаете значение с помощью удаления подстроки #*=

Jason S 03.12.2017 04:01

Вы правы, поскольку я создаю переменную PARAMS, мне вообще не нужно использовать shift

Masadow 05.12.2017 12:17

Другое решение без getopt [s], POSIX, старый стиль Unix

Подобно решение Бруно Броноски опубликовал, здесь не используется getopt(s).

Основная отличительная черта моего решения заключается в том, что оно позволяет объединять параметры вместе, как tar -xzf foo.tar.gz равно tar -x -z -f foo.tar.gz. И так же, как в tar, ps и т. д., Начальный дефис не является обязательным для блока коротких опций (но это можно легко изменить). Также поддерживаются длинные параметры (но если блок начинается с единицы, требуются два начальных дефиса).

Код с примерами опций

#!/bin/sh

echo
echo "POSIX-compliant getopt(s)-free old-style-supporting option parser from phk@[se.unix]"
echo

print_usage() {
  echo "Usage:

  $0 {a|b|c} [ARG...]

Options:

  --aaa-0-args
  -a
    Option without arguments.

  --bbb-1-args ARG
  -b ARG
    Option with one argument.

  --ccc-2-args ARG1 ARG2
  -c ARG1 ARG2
    Option with two arguments.

" >&2
}

if [ $# -le 0 ]; then
  print_usage
  exit 1
fi

opt=
while :; do

  if [ $# -le 0 ]; then

    # no parameters remaining -> end option parsing
    break

  elif [ ! "$opt" ]; then

    # we are at the beginning of a fresh block
    # remove optional leading hyphen and strip trailing whitespaces
    opt=$(echo "$1" | sed 's/^-\?\([a-zA-Z0-9\?-]*\)/\1/')

  fi

  # get the first character -> check whether long option
  first_chr=$(echo "$opt" | awk '{print substr($1, 1, 1)}')
  [ "$first_chr" = - ] && long_option=T || long_option=F

  # note to write the options here with a leading hyphen less
  # also do not forget to end short options with a star
  case $opt in

    -)

      # end of options
      shift
      break
      ;;

    a*|-aaa-0-args)

      echo "Option AAA activated!"
      ;;

    b*|-bbb-1-args)

      if [ "$2" ]; then
        echo "Option BBB with argument '$2' activated!"
        shift
      else
        echo "BBB parameters incomplete!" >&2
        print_usage
        exit 1
      fi
      ;;

    c*|-ccc-2-args)

      if [ "$2" ] && [ "$3" ]; then
        echo "Option CCC with arguments '$2' and '$3' activated!"
        shift 2
      else
        echo "CCC parameters incomplete!" >&2
        print_usage
        exit 1
      fi
      ;;

    h*|\?*|-help)

      print_usage
      exit 0
      ;;

    *)

      if [ "$long_option" = T ]; then
        opt=$(echo "$opt" | awk '{print substr($1, 2)}')
      else
        opt=$first_chr
      fi
      printf 'Error: Unknown option: "%s"\n' "$opt" >&2
      print_usage
      exit 1
      ;;

  esac

  if [ "$long_option" = T ]; then

    # if we had a long option then we are going to get a new block next
    shift
    opt=

  else

    # if we had a short option then just move to the next character
    opt=$(echo "$opt" | awk '{print substr($1, 2)}')

    # if block is now empty then shift to the next one
    [ "$opt" ] || shift

  fi

done

echo "Doing something..."

exit 0

Для примера использования см. Примеры ниже.

Расположение вариантов с аргументами

Как бы то ни было, варианты с аргументами не являются последними (должны быть только длинные варианты). Так что, например, в tar (по крайней мере, в некоторых реализациях) параметры f должны быть последними, потому что следует имя файла (tar xzf bar.tar.gz работает, а tar xfz bar.tar.gz нет), здесь это не так (см. последующие примеры).

Несколько вариантов с аргументами

В качестве еще одного бонуса параметры опций расходуются в порядке опций по параметрам с необходимыми опциями. Просто посмотрите на вывод моего скрипта здесь с командной строкой abc X Y Z (или -abc X Y Z):

Option AAA activated!
Option BBB with argument 'X' activated!
Option CCC with arguments 'Y' and 'Z' activated!

Длинные варианты тоже объединены

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

  • -cba Z Y X
  • cba Z Y X
  • -cb-aaa-0-args Z Y X
  • -c-bbb-1-args Z Y X -a
  • --ccc-2-args Z Y -ba X
  • c Z Y b X a
  • -c Z Y -b X -a
  • --ccc-2-args Z Y --bbb-1-args X --aaa-0-args

Все это приводит к:

Option CCC with arguments 'Z' and 'Y' activated!
Option BBB with argument 'X' activated!
Option AAA activated!
Doing something...

Не в этом решении

Необязательные аргументы

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

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

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

Аргументы опций, введенные со знаком равенства

Как и в случае с необязательными аргументами, я не являюсь поклонником этого (кстати, есть ли ветка для обсуждения плюсов и минусов разных стилей параметров?), Но если вы хотите этого, вы, вероятно, можете реализовать это самостоятельно, как это было сделано в http://mywiki.wooledge.org/BashFAQ/035#Manual_loop с --long-with-arg=?* case и затем убирая знак равенства (это, кстати, сайт, который говорит, что объединение параметров возможно с некоторыми усилиями, но «оставил [это] в качестве упражнения для читателя», что заставило меня поверить им на слово, но я начал с царапать).

Прочие примечания

Совместимость с POSIX, работает даже на старых установках Busybox, с которыми мне приходилось иметь дело (например, отсутствовали cut, head и getopts).

Обратите внимание, что getopt(1) был недолгой ошибкой от AT&T.

getopt был создан в 1984 году, но уже похоронен в 1986 году, потому что его нельзя было использовать.

Доказательством того, что getopt очень устарел, является то, что на странице руководства getopt(1) все еще упоминается "$*" вместо "$@", который был добавлен в оболочку Bourne в 1986 году вместе со встроенной оболочкой getopts(1), чтобы иметь дело с аргументами с пробелами внутри.

Кстати: если вас интересует анализ длинных параметров в сценариях оболочки, может быть интересно узнать, что реализация getopt(3) из libc (Solaris) и ksh93 добавили унифицированную реализацию длинных параметров, которая поддерживает длинные параметры в качестве псевдонимов для коротких параметров. Это заставляет ksh93 и Bourne Shell реализовывать единый интерфейс для длинных опций через getopts.

Пример длинных опций, взятых из справочной страницы Bourne Shell:

getopts "f:(file)(input-file)o:(output-file)" OPTX "$@"

показывает, как долго можно использовать псевдонимы опций как в Bourne Shell, так и в ksh93.

См. Справочную страницу недавней Bourne Shell:

http://schillix.sourceforge.net/man/man1/bosh.1.html

и страница руководства для getopt (3) из OpenSolaris:

http://schillix.sourceforge.net/man/man3c/getopt.3c.html

и, наконец, справочная страница getopt (1) для проверки устаревшего $ *:

http://schillix.sourceforge.net/man/man1/getopt.1.html

deploy.sh

#!/bin/bash

while [[ "$#" -gt 0 ]]; do
    case  in
        -t|--target) target = ""; shift ;;
        -u|--uglify) uglify=1 ;;
        *) echo "Unknown parameter passed: "; exit 1 ;;
    esac
    shift
done

echo "Where to deploy: $target"
echo "Should uglify  : $uglify"

Использование:

./deploy.sh -t dev -u

# OR:

./deploy.sh --target dev --uglify

Вот что я делаю. Придется while [[ "$#" > 1 ]], если я хочу поддержать завершение строки логическим флагом ./script.sh --debug dev --uglify fast --verbose. Пример: gist.github.com/hfossli/4368aa5a577742c3c9f9266ed214aa58

hfossli 07.04.2018 23:58

Ух ты! Просто и чисто! Вот как я использую это: gist.github.com/hfossli/4368aa5a577742c3c9f9266ed214aa58

hfossli 08.04.2018 00:10

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

RealHandy 31.01.2019 23:05

Предупреждение: это допускает повторяющиеся аргументы, последний аргумент преобладает. например ./script.sh -d dev -d prod приведет к deploy == 'prod'. Я все равно использовал: P :): +1:

yair 16.09.2019 01:33

Я использую это (спасибо!), Но обратите внимание, что он допускает пустое значение аргумента, например. ./script.sh -d не будет генерировать ошибку, а просто установит $deploy в пустую строку.

EM0 12.01.2020 10:26

Отличный ответ, спасибо! Немного укоротил - while (( "$#" )); do вместо while [[ "$#" -gt 0 ]]; do

CIsForCookies 07.01.2021 19:53

@CIsForCookies Спасибо! Это потому, что, afaik, синтаксис ((...)) существует только в ksh, bash и zsh, но не в обычном sh.

inanc 07.01.2021 20:36

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

  1. Это чистое решение Bash, без дополнительных утилит.
  2. Не загрязняет глобальную область.
  3. Легко возвращает вам простые в использовании переменные, на которых вы могли бы построить дальнейшую логику.
  4. Количество тире перед параметрами не имеет значения (--all равно -all равно all=all)

Приведенный ниже сценарий представляет собой демонстрацию работы с копированием и вставкой. См. Функцию show_use, чтобы понять, как использовать parse_params.

Ограничения:

  1. Не поддерживает параметры, разделенные пробелами (-d 1)
  2. Имена параметров будут терять тире, поэтому --any-param и -anyparam эквивалентны
  3. eval $(parse_params "$@") должен использоваться внутри bash функция (он не будет работать в глобальной области)

#!/bin/bash

# Universal Bash parameter parsing
# Parse equal sign separated params into named local variables
# Standalone named parameter value will equal its param name (--force creates variable $force= = "force")
# Parses multi-valued named params into an array (--path=path1 --path=path2 creates ${path[*]} array)
# Puts un-named params as-is into ${ARGV[*]} array
# Additionally puts all named params as-is into ${ARGN[*]} array
# Additionally puts all standalone "option" params as-is into ${ARGO[*]} array
# @author Oleksii Chekulaiev
# @version v1.4.1 (Jul-27-2018)
parse_params ()
{
    local existing_named
    local ARGV=() # un-named params
    local ARGN=() # named params
    local ARGO=() # options (--params)
    echo "local ARGV=(); local ARGN=(); local ARGO=();"
    while [[ "" != "" ]]; do
        # Escape asterisk to prevent bash asterisk expansion, and quotes to prevent string breakage
        _escaped=${1/\*/\'\"*\"\'}
        _escaped=${_escaped//\'/\\'}
        _escaped=${_escaped//\"/\\"}
        # If equals delimited named parameter
        nonspace = "[^[:space:]]"
        if [[ "" =~ ^${nonspace}${nonspace}*=..* ]]; then
            # Add to named parameters array
            echo "ARGN+=('$_escaped');"
            # key is part before first =
            local _key=$(echo "" | cut -d = -f 1)
            # Just add as non-named when key is empty or contains space
            if [[ "$_key" == "" || "$_key" =~ " " ]]; then
                echo "ARGV+=('$_escaped');"
                shift
                continue
            fi
            # val is everything after key and = (protect from param==value error)
            local _val = "${1/$_key=}"
            # remove dashes from key name
            _key=${_key//\-}
            # skip when key is empty
            # search for existing parameter name
            if (echo "$existing_named" | grep "\b$_key\b" >/dev/null); then
                # if name already exists then it's a multi-value named parameter
                # re-declare it as an array if needed
                if ! (declare -p _key 2> /dev/null | grep -q 'declare \-a'); then
                    echo "$_key=(\"$$_key\");"
                fi
                # append new value
                echo "$_key+=('$_val');"
            else
                # single-value named parameter
                echo "local $_key='$_val';"
                existing_named = " $_key"
            fi
        # If standalone named parameter
        elif [[ "" =~ ^\-${nonspace}+ ]]; then
            # remove dashes
            local _key=${1//\-}
            # Just add as non-named when key is empty or contains space
            if [[ "$_key" == "" || "$_key" =~ " " ]]; then
                echo "ARGV+=('$_escaped');"
                shift
                continue
            fi
            # Add to options array
            echo "ARGO+=('$_escaped');"
            echo "local $_key=\"$_key\";"
        # non-named parameter
        else
            # Escape asterisk to prevent bash asterisk expansion
            _escaped=${1/\*/\'\"*\"\'}
            echo "ARGV+=('$_escaped');"
        fi
        shift
    done
}

#--------------------------- DEMO OF THE USAGE -------------------------------

show_use ()
{
    eval $(parse_params "$@")
    # --
    echo "${ARGV[0]}" # print first unnamed param
    echo "${ARGV[1]}" # print second unnamed param
    echo "${ARGN[0]}" # print first named param
    echo "${ARG0[0]}" # print first option param (--force)
    echo "$anyparam"  # print --anyparam value
    echo "$k"         # print k=5 value
    echo "${multivalue[0]}" # print first value of multi-value
    echo "${multivalue[1]}" # print second value of multi-value
    [[ "$force" == "force" ]] && echo "$force is set so let the force be with you"
}

show_use "param 1" --anyparam = "my value" param2 k=5 --force --multi-value=test1 --multi-value=test2

Чтобы использовать демонстрацию для анализа параметров, которые входят в ваш сценарий bash, вы просто выполняете show_use "$@"

Oleksii Chekulaiev 28.09.2016 15:55

В основном я обнаружил, что github.com/renatosilva/easyoptions делает то же самое, но немного более массивно, чем эта функция.

Oleksii Chekulaiev 28.09.2016 15:58

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

https://argbash.io

Спасибо, что написали argbash, я просто использовал его и обнаружил, что он хорошо работает. В основном я выбрал argbash, потому что это генератор кода, поддерживающий более старую версию bash 3.x, которая есть в OS X 10.11 El Capitan. Единственным недостатком является то, что подход с генератором кода означает довольно много кода в вашем основном скрипте по сравнению с вызовом модуля.

RichVel 18.08.2016 08:34

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

bubla 23.08.2016 23:40

Хорошо знать. Этот пример интересен, но все еще не совсем понятен - возможно, вы можете изменить имя сгенерированного скрипта на 'parse_lib.sh' или подобное и показать, где его вызывает основной скрипт (например, в разделе обертывания скрипта, который является более сложным вариантом использования).

RichVel 24.08.2016 08:47

Проблемы устранены в последней версии argbash: улучшена документация, представлен сценарий быстрого запуска argbash-init, и вы даже можете использовать argbash в Интернете по адресу argbash.io/generate

bubla 02.12.2016 23:12

Главный ответ на этот вопрос показался мне немного ошибочным, когда я его попробовал - вот мое решение, которое я нашел более надежным:

boolean_arg = ""
arg_with_value = ""

while [[ $# -gt 0 ]]
do
key = "$1"
case $key in
    -b|--boolean-arg)
    boolean_arg=true
    shift
    ;;
    -a|--arg-with-value)
    arg_with_value = "$2"
    shift
    shift
    ;;
    -*)
    echo "Unknown option: $1"
    exit 1
    ;;
    *)
    arg_num=$(( $arg_num + 1 ))
    case $arg_num in
        1)
        first_normal_arg = "$1"
        shift
        ;;
        2)
        second_normal_arg = "$1"
        shift
        ;;
        *)
        bad_args=TRUE
    esac
    ;;
esac
done

# Handy to have this here when adding arguments to
# see if they're working. Just edit the '0' to be '1'.
if [[ 0 == 1 ]]; then
    echo "first_normal_arg: $first_normal_arg"
    echo "second_normal_arg: $second_normal_arg"
    echo "boolean_arg: $boolean_arg"
    echo "arg_with_value: $arg_with_value"
    exit 0
fi

if [[ $bad_args == TRUE || $arg_num < 2 ]]; then
    echo "Usage: $(basename "$0") <first-normal-arg> <second-normal-arg> [--boolean-arg] [--arg-with-value VALUE]"
    exit 1
fi

Решение, сохраняющее необработанные аргументы. Демоверсии включены.

Вот мое решение. Он ОЧЕНЬ гибкий и, в отличие от других, не требует внешних пакетов и аккуратно обрабатывает оставшиеся аргументы.

Использование: ./myscript -flag flagvariable -otherflag flagvar2

Все, что вам нужно сделать, это отредактировать строку validflags. Он добавляет дефис и ищет все аргументы. Затем он определяет следующий аргумент как имя флага, например.

./myscript -flag flagvariable -otherflag flagvar2
echo $flag $otherflag
flagvariable flagvar2

Основной код (короткая версия, подробная с примерами ниже, также версия с ошибками):

#!/usr/bin/env bash
#shebang.io
validflags = "rate time number"
count=1
for arg in $@
do
    match=0
    argval=
    for flag in $validflags
    do
        sflag = "-"$flag
        if [ "$argval" == "$sflag" ]
        then
            declare $flag=
            match=1
        fi
    done
        if [ "$match" == "1" ]
    then
        shift 2
    else
        leftovers=$(echo $leftovers $argval)
        shift
    fi
    count=$(($count+1))
done
#Cleanup then restore the leftovers
shift $#
set -- $leftovers

Подробная версия со встроенными демонстрациями эха:

#!/usr/bin/env bash
#shebang.io
rate=30
time=30
number=30
echo "all args
$@"
validflags = "rate time number"
count=1
for arg in $@
do
    match=0
    argval=
#   argval=$(echo $@ | cut -d ' ' -f$count)
    for flag in $validflags
    do
            sflag = "-"$flag
        if [ "$argval" == "$sflag" ]
        then
            declare $flag=
            match=1
        fi
    done
        if [ "$match" == "1" ]
    then
        shift 2
    else
        leftovers=$(echo $leftovers $argval)
        shift
    fi
    count=$(($count+1))
done

#Cleanup then restore the leftovers
echo "pre final clear args:
$@"
shift $#
echo "post final clear args:
$@"
set -- $leftovers
echo "all post set args:
$@"
echo arg1:  arg2: 

echo leftovers: $leftovers
echo rate $rate time $time number $number

Последний, он выдает ошибку, если передан недопустимый аргумент.

#!/usr/bin/env bash
#shebang.io
rate=30
time=30
number=30
validflags = "rate time number"
count=1
for arg in $@
do
    argval=
    match=0
        if [ "${argval:0:1}" == "-" ]
    then
        for flag in $validflags
        do
                sflag = "-"$flag
            if [ "$argval" == "$sflag" ]
            then
                declare $flag=
                match=1
            fi
        done
        if [ "$match" == "0" ]
        then
            echo "Bad argument: $argval"
            exit 1
        fi
        shift 2
    else
        leftovers=$(echo $leftovers $argval)
        shift
    fi
    count=$(($count+1))
done
#Cleanup then restore the leftovers
shift $#
set -- $leftovers
echo rate $rate time $time number $number
echo leftovers: $leftovers

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

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

В этом примере показано, как использовать getopt и eval, а также HEREDOC и shift для обработки коротких и длинных параметров с указанным ниже обязательным значением и без него. Кроме того, оператор switch / case краток и прост для понимания.

#!/usr/bin/env bash

# usage function
function usage()
{
   cat << HEREDOC

   Usage: $progname [--num NUM] [--time TIME_STR] [--verbose] [--dry-run]

   optional arguments:
     -h, --help           show this help message and exit
     -n, --num NUM        pass in a number
     -t, --time TIME_STR  pass in a time string
     -v, --verbose        increase the verbosity of the bash script
     --dry-run            do a dry run, dont change any files

HEREDOC
}  

# initialize variables
progname=$(basename $0)
verbose=0
dryrun=0
num_str=
time_str=

# use getopt and store the output into $OPTS
# note the use of -o for the short options, --long for the long name options
# and a : for any option that takes a parameter
OPTS=$(getopt -o "hn:t:v" --long "help,num:,time:,verbose,dry-run" -n "$progname" -- "$@")
if [ $? != 0 ] ; then echo "Error in command line arguments." >&2 ; usage; exit 1 ; fi
eval set -- "$OPTS"

while true; do
  # uncomment the next line to see how shift is working
  # echo "\$1:\"$1\" \$2:\"$2\""
  case "$1" in
    -h | --help ) usage; exit; ;;
    -n | --num ) num_str = "$2"; shift 2 ;;
    -t | --time ) time_str = "$2"; shift 2 ;;
    --dry-run ) dryrun=1; shift ;;
    -v | --verbose ) verbose=$((verbose + 1)); shift ;;
    -- ) shift; break ;;
    * ) break ;;
  esac
done

if (( $verbose > 0 )); then

   # print out all the parameters we read in
   cat <<EOM
   num=$num_str
   time=$time_str
   verbose=$verbose
   dryrun=$dryrun
EOM
fi

# The rest of your script below

Наиболее важные строки приведенного выше сценария:

OPTS=$(getopt -o "hn:t:v" --long "help,num:,time:,verbose,dry-run" -n "$progname" -- "$@")
if [ $? != 0 ] ; then echo "Error in command line arguments." >&2 ; exit 1 ; fi
eval set -- "$OPTS"

while true; do
  case "$1" in
    -h | --help ) usage; exit; ;;
    -n | --num ) num_str = "$2"; shift 2 ;;
    -t | --time ) time_str = "$2"; shift 2 ;;
    --dry-run ) dryrun=1; shift ;;
    -v | --verbose ) verbose=$((verbose + 1)); shift ;;
    -- ) shift; break ;;
    * ) break ;;
  esac
done

Коротко, по существу, читабельно и обрабатывает практически все (ИМХО).

Надеюсь, это кому-то поможет.

Это один из лучших ответов.

Mr. Polywhirl 05.03.2020 15:30

# As long as there is at least one more argument, keep looping
while [[ $# -gt 0 ]]; do
    key = ""
    case "$key" in
        # This is a flag type option. Will catch either -f or --foo
        -f|--foo)
        FOO=1
        ;;
        # Also a flag type option. Will catch either -b or --bar
        -b|--bar)
        BAR=1
        ;;
        # This is an arg value type option. Will catch -o value or --output-file value
        -o|--output-file)
        shift # past the key and to the value
        OUTPUTFILE = ""
        ;;
        # This is an arg=value type option. Will catch -o=value or --output-file=value
        -o=*|--output-file=*)
        # No need to shift here since the value is part of the same string
        OUTPUTFILE = "${key#*=}"
        ;;
        *)
        # Do whatever you want with extra options
        echo "Unknown option '$key'"
        ;;
    esac
    # Shift after checking all the cases to get the next option
    shift
done

Это позволяет вам иметь как параметры / значения, разделенные пробелом, так и одинаковые определенные значения.

Итак, вы можете запустить свой скрипт, используя:

./myscript --foo -b -o /fizz/file.txt

а также:

./myscript -f --bar -o=/fizz/file.txt

и оба должны иметь одинаковый конечный результат.

ЗА:

  • Допускает как -arg = значение, так и -arg значение

  • Работает с любым именем аргумента, которое вы можете использовать в bash

    • Значение -a или -arg, или --arg, или -a-r-g, или что-то еще
  • Чистый баш. Не нужно изучать / использовать getopt или getopts

МИНУСЫ:

  • Невозможно объединить аргументы

    • Значение no -abc. Вы должны сделать -a -b -c

Я написал помощника bash, чтобы написать хороший инструмент bash

главная страница проекта: https://gitlab.mbedsys.org/mbedsys/bashopts

пример:

#!/bin/bash -ei

# load the library
. bashopts.sh

# Enable backtrace dusplay on error
trap 'bashopts_exit_handle' ERR

# Initialize the library
bashopts_setup -n "$0" -d "This is myapp tool description displayed on help message" -s "$HOME/.config/myapprc"

# Declare the options
bashopts_declare -n first_name -l first -o f -d "First name" -t string -i -s -r
bashopts_declare -n last_name -l last -o l -d "Last name" -t string -i -s -r
bashopts_declare -n display_name -l display-name -t string -d "Display name" -e "\$first_name \$last_name"
bashopts_declare -n age -l number -d "Age" -t number
bashopts_declare -n email_list -t string -m add -l email -d "Email adress"

# Parse arguments
bashopts_parse_args "$@"

# Process argument
bashopts_process_args

окажет помощь:

NAME:
    ./example.sh - This is myapp tool description displayed on help message

USAGE:
    [options and commands] [-- [extra args]]

OPTIONS:
    -h,--help                          Display this help
    -n,--non-interactive true          Non interactive mode - [$bashopts_non_interactive] (type:boolean, default:false)
    -f,--first "John"                  First name - [$first_name] (type:string, default:"")
    -l,--last "Smith"                  Last name - [$last_name] (type:string, default:"")
    --display-name "John Smith"        Display name - [$display_name] (type:string, default:"$first_name $last_name")
    --number 0                         Age - [$age] (type:number, default:0)
    --email                            Email adress - [$email_list] (type:string, default:"")

наслаждаться :)

Я получаю это в Mac OS X: `` lib / bashopts.sh: line 138: declare: -A: invalid option declare: usage: declare [-afFirtx] [-p] [name [= value] ...] Ошибка в lib / bashopts.sh: 138. 'declare -x -A bashopts_optprop_name' завершился со статусом 2 Дерево вызовов: 1: lib / controller.sh: 4 source (...) Выход со статусом 1 ''

Josh Wulf 24.06.2017 21:07

Для использования вам потребуется Bash версии 4. На Mac версия по умолчанию - 3. Вы можете использовать home brew для установки bash 4.

Josh Wulf 24.06.2017 21:17

Вот мой подход - использование регулярного выражения.

  • нет подвигов
  • обрабатывает блок коротких параметров -qwerty
  • обрабатывает короткие параметры -q -w -e
  • обрабатывает длинные варианты --qwerty
  • вы можете передать атрибут короткой или длинной опции (если вы используете блок коротких опций, атрибут прикрепляется к последней опции)
  • вы можете использовать пробелы или = для предоставления атрибутов, но атрибуты совпадают до тех пор, пока не встретите дефис + пробел «разделитель», поэтому в --q=qwe tyqwe ty является одним атрибутом
  • он обрабатывает сочетание всего вышеперечисленного, поэтому -o a -op attr ibute --option=att ribu te --op-tion attribute --option att-ribute действителен

сценарий:

#!/usr/bin/env sh

help_menu() {
  echo "Usage:

  ${0##*/} [-h][-l FILENAME][-d]

Options:

  -h, --help
    display this help and exit

  -l, --logfile=FILENAME
    filename

  -d, --debug
    enable debug
  "
}

parse_options() {
  case $opt in
    h|help)
      help_menu
      exit
     ;;
    l|logfile)
      logfile=${attr}
      ;;
    d|debug)
      debug=true
      ;;
    *)
      echo "Unknown option: ${opt}\nRun ${0##*/} -h for help.">&2
      exit 1
  esac
}
options=$@

until [ "$options" = "" ]; do
  if [[ $options =~ (^ *(--([a-zA-Z0-9-]+)|-([a-zA-Z0-9-]+))(( |=)(([\_\.\?/\a-zA-Z0-9]?[ -]?[\_\.\?a-zA-Z0-9]+)+))?(.*)|(.+)) ]]; then
    if [[ ${BASH_REMATCH[3]} ]]; then # for --option[=][attribute] or --option[=][attribute]
      opt=${BASH_REMATCH[3]}
      attr=${BASH_REMATCH[7]}
      options=${BASH_REMATCH[9]}
    elif [[ ${BASH_REMATCH[4]} ]]; then # for block options -qwert[=][attribute] or single short option -a[=][attribute]
      pile=${BASH_REMATCH[4]}
      while (( ${#pile} > 1 )); do
        opt=${pile:0:1}
        attr = ""
        pile=${pile/${pile:0:1}/}
        parse_options
      done
      opt=$pile
      attr=${BASH_REMATCH[7]}
      options=${BASH_REMATCH[9]}
    else # leftovers that don't match
      opt=${BASH_REMATCH[10]}
      options = ""
    fi
    parse_options
  fi
done

Как этот. Может быть, просто добавьте параметр -e для вывода с новой строки.

mauron85 21.06.2017 09:03

Предположим, мы создали сценарий оболочки с именем test_args.sh, как показано ниже.

#!/bin/sh
until [ $# -eq 0 ]
do
  name=${1:1}; shift;
  if [[ -z "" ||  == -* ]] ; then eval "export $name=true"; else eval "export $name = "; shift; fi  
done
echo "year=$year month=$month day=$day flag=$flag"

После запускаем следующую команду:

sh test_args.sh  -year 2017 -flag  -month 12 -day 22 

Результатом будет:

year=2017 month=12 day=22 flag=true

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

Will Barnwell 11.10.2017 02:57

Я хочу отправить свой проект: https://github.com/flyingangel/argparser

source argparser.sh
parse_args "$@"

Просто как тот. Среда будет заполнена переменными с тем же именем, что и аргументы

Просто и легко изменить, параметры могут быть в любом порядке. это можно изменить, чтобы принимать параметры в любой форме (-a, --a, a и т. д.).

for arg in "$@"
do
   key=$(echo $arg | cut -f1 -d=)`
   value=$(echo $arg | cut -f2 -d=)`
   case "$key" in
        name|-name)      read_name=$value;;
        id|-id)          read_id=$value;;
        *)               echo "I dont know what to do with this"
   ease
done

В этом скрипте ease неверен и должен быть заменен на esac, чтобы закрыть блок case

thibmaek 05.06.2019 10:57

Расширяя ответ @bruno-bronosky, я добавил «препроцессор» для обработки некоторого общего форматирования:

  • Расширяет --longopt=val на --longopt val
  • Расширяет -xyz на -x -y -z
  • Поддерживает -- для обозначения конца флагов
  • Показывает ошибку для неожиданных опций
  • Компактный и легко читаемый переключатель опций
#!/bin/bash

# Report usage
usage() {
  echo "Usage:"
  echo "$(basename $0) [options] [--] [file1, ...]"

  # Optionally exit with a status code
  if [ -n "$1" ]; then
    exit "$1"
  fi
}

invalid() {
  echo "ERROR: Unrecognized argument: $1" >&2
  usage 1
}

# Pre-process options to:
# - expand -xyz into -x -y -z
# - expand --longopt=arg into --longopt arg
ARGV=()
END_OF_OPT=
while [[ $# -gt 0 ]]; do
  arg = "$1"; shift
  case "${END_OF_OPT}${arg}" in
    --) ARGV+=("$arg"); END_OF_OPT=1 ;;
    --*=*)ARGV+=("${arg%%=*}" "${arg#*=}") ;;
    --*) ARGV+=("$arg"); END_OF_OPT=1 ;;
    -*) for i in $(seq 2 ${#arg}); do ARGV+=("-${arg:i-1:1}"); done ;;
    *) ARGV+=("$arg") ;;
  esac
done

# Apply pre-processed options
set -- "${ARGV[@]}"

# Parse options
END_OF_OPT=
POSITIONAL=()
while [[ $# -gt 0 ]]; do
  case "${END_OF_OPT}${1}" in
    -h|--help)      usage 0 ;;
    -p|--password)  shift; PASSWORD = "$1" ;;
    -u|--username)  shift; USERNAME = "$1" ;;
    -n|--name)      shift; names+=("$1") ;;
    -q|--quiet)     QUIET=1 ;;
    -C|--copy)      COPY=1 ;;
    -N|--notify)    NOTIFY=1 ;;
    --stdin)        READ_STDIN=1 ;;
    --)             END_OF_OPT=1 ;;
    -*)             invalid "$1" ;;
    *)              POSITIONAL+=("$1") ;;
  esac
  shift
done

# Restore positional parameters
set -- "${POSITIONAL[@]}"

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

В основном eval "local key='val'"

function myrsync() {

        local backup=("${@}") args=(); while [[ $# -gt 0 ]]; do k = "";
                case "$k" in
                    ---sourceuser|---sourceurl|---targetuser|---targeturl|---file|---exclude|---include)
                        eval "local ${k:3}=''"; shift; shift    # Past two arguments
                    ;;
                    *)  # Unknown option  
                        args+=(""); shift;                        # Past argument only
                    ;;                                              
                esac                                                
        done; set -- "${backup[@]}"                                 # Restore $@


        echo "${sourceurl}"
}

Объявляет переменные как локальные, а не глобальные, как в большинстве ответов здесь.

Называется как:

myrsync ---sourceurl http://abc.def.g ---sourceuser myuser ... 

$ {K: 3} - это в основном подстрока для удаления первого --- из ключа.

Есть несколько способов синтаксического анализа аргументов cmdline (например, GNU getopt (не переносимый) vs BSD (MacOS) getopt vs getopts) - все это проблематично. Это решение

  • портативный!
  • не имеет никаких зависимостей, полагается только на встроенные модули bash
  • позволяет использовать как короткие, так и длинные варианты
  • обрабатывает пробелы или одновременно использует разделитель = между опцией и аргументом
  • поддерживает объединенный короткий вариант стиля -vxf
  • обрабатывает опцию с необязательными аргументами (например, --color против --color=always),
  • правильно обнаруживает и сообщает о неизвестных параметрах
  • поддерживает --, чтобы сигнализировать об окончании опций, и
  • не требует раздувания кода по сравнению с альтернативами для того же набора функций. Т.е. лаконичный, а значит, более легкий в уходе

Примеры: любой из

# flag
-f
--foo

# option with required argument
-b"Hello World"
-b "Hello World"
--bar "Hello World"
--bar = "Hello World"

# option with optional argument
--baz
--baz = "Optional Hello"

#!/usr/bin/env bash

usage() {
  cat - >&2 <<EOF
NAME
    program-name.sh - Brief description
 
SYNOPSIS
    program-name.sh [-h|--help]
    program-name.sh [-f|--foo]
                    [-b|--bar <arg>]
                    [--baz[=<arg>]]
                    [--]
                    FILE ...

REQUIRED ARGUMENTS
  FILE ...
          input files

OPTIONS
  -h, --help
          Prints this and exits

  -f, --foo
          A flag option
      
  -b, --bar <arg>
          Option requiring an argument <arg>

  --baz[=<arg>]
          Option that has an optional argument <arg>. If <arg>
          is not specified, defaults to 'DEFAULT'
  --     
          Specify end of options; useful if the first non option
          argument starts with a hyphen

EOF
}

fatal() {
    for i; do
        echo -e "${i}" >&2
    done
    exit 1
}

# For long option processing
next_arg() {
    if [[ $OPTARG == *=* ]]; then
        # for cases like '--opt=arg'
        OPTARG = "${OPTARG#*=}"
    else
        # for cases like '--opt arg'
        OPTARG = "${args[$OPTIND]}"
        OPTIND=$((OPTIND + 1))
    fi
}

# ':' means preceding option character expects one argument, except
# first ':' which make getopts run in silent mode. We handle errors with
# wildcard case catch. Long options are considered as the '-' character
optspec = ":hfb:-:"
args=("" "$@")  # dummy first element so $1 and $args[1] are aligned
while getopts "$optspec" optchar; do
    case "$optchar" in
        h) usage; exit 0 ;;
        f) foo=1 ;;
        b) bar = "$OPTARG" ;;
        -) # long option processing
            case "$OPTARG" in
                help)
                    usage; exit 0 ;;
                foo)
                    foo=1 ;;
                bar|bar=*) next_arg
                    bar = "$OPTARG" ;;
                baz)
                    baz=DEFAULT ;;
                baz=*) next_arg
                    baz = "$OPTARG" ;;
                -) break ;;
                *) fatal "Unknown option '--${OPTARG}'" "see '${0} --help' for usage" ;;
            esac
            ;;
        *) fatal "Unknown option: '-${OPTARG}'" "See '${0} --help' for usage" ;;
    esac
done

shift $((OPTIND-1))

if [ "$#" -eq 0 ]; then
    fatal "Expected at least one required argument FILE" \
    "See '${0} --help' for usage"
fi

echo "foo=$foo, bar=$bar, baz=$baz, files=${@}"

Хотел поделиться, что сделал для разбора вариантов. Некоторые из моих потребностей не были удовлетворены приведенными здесь ответами, поэтому мне пришлось придумать следующее: https://github.com/MihirLuthra/bash_option_parser

Это поддерживает:

  • Разбор подопции
  • Псевдонимы для опций
  • Необязательные аргументы
  • Переменные аргументы
  • Использование печати и ошибки

Допустим, у нас есть команда fruit, которая используется следующим образом:

fruit <fruit-name> ...
   [-e|—-eat|—-chew]
   [-c|--cut <how> <why>]
   <command> [<args>] 

-e не принимает аргументов -c принимает два аргумента, то есть как резать и зачем резать Сам fruit принимает по крайней мере один аргумент. <command> предназначен для подопций, таких как apple, orange и т. д. (Аналогично git, у которого есть подопции commit, push и т. д.)

Итак, чтобы разобрать его:

parse_options \
    'fruit'                         '1 ...'  \
    '-e'     , '--eat' , '--chew'   '0'      \
    '-c'     , '--cut'              '1 1'    \
    'apple'                         'S'      \
    'orange'                        'S'      \
    ';' \
    "$@"

Теперь, если возникла какая-либо ошибка использования, ее можно распечатать с помощью option_parser_error_msg следующим образом:

retval=$?

if [ $retval -ne 0 ]; then
    # this will manage error messages if
    # insufficient or extra args are supplied

    option_parser_error_msg "$retval"

    # This will print the usage
    print_usage 'fruit'
    exit 1
fi

Чтобы проверить, были ли переданы какие-то параметры,

if [ -n "${OPTIONS[-c]}" ]
then
    echo "-c was passed"

    # args can be accessed in a 2D-array-like format
    echo "Arg1 to -c = ${ARGS[-c,0]}"
    echo "Arg2 to -c = ${ARGS[-c,1]}"

fi

Синтаксический анализ подопции также может быть выполнен путем передачи $shift_count в parse_options_detailed, что заставляет его начать синтаксический анализ после сдвига аргументов для достижения аргументов подопции. Это продемонстрировано в этом пример.

Подробное описание приведено в ридми и примерах. в хранилище.

Меня вдохновил относительно простой ответ @bronson, и я хотел попытаться улучшить его (не добавляя излишней сложности). Вот результат:

Другой синтаксический анализатор аргументов оболочки (ASAP) - POSIX, без getopt*

  • Используйте любой из стилей опций -n [arg], -abn [arg], --name [arg]и--name=arg;
  • Аргументы могут появляться в любом порядке, только позиционные остались в$@ после цикла;
  • Использовать--заставить оставшиеся аргументы должны рассматриваться как позиционные;
  • Обнаруживает недопустимые параметры и отсутствующие аргументы;
  • Не зависит от getopt(s) или внешних инструментов (одна функция использует простую команду sed);
  • Портативный, компактный, достаточно читаемый, с независимые функции.
# Convenience functions.
usage_error () { echo >&2 "$(basename $0):  $1"; exit 2; }
assert_argument () { test "$1" != "$EOL" || usage_error "$2 requires an argument"; }

# One loop, nothing more.
EOL=$(echo '\01\03\03\07')
if [ "$#" != 0 ]; then
  set -- "$@" "$EOL"
  while [ "$1" != "$EOL" ]; do
    opt = "$1"; shift
    case "$opt" in

      # Your options go here.
      -f|--flag) flag=true;;
      -n|--name) assert_argument "$1" $opt; name = "$1"; shift;;

      -|''|[^-]*) set -- "$@" "$opt";;                                          # positional argument, rotate to the end
      # Extra features (you may remove any line you don't need):
      --*=*)      set -- "${opt%%=*}" "${opt#*=}" "$@";;                        # convert '--name=arg' to '--name' 'arg'
      -[^-]?*)    set -- $(echo "${opt#-}" | sed 's/\(.\)/ -\1/g') "$@";;       # convert '-abc' to '-a' '-b' '-c'
      --)         while [ "$1" != "$EOL" ]; do set -- "$@" "$1"; shift; done;;  # process remaining arguments as positional
      -*)         usage_error "unknown option: '$opt'";;                        # catch misspelled options
      *)          usage_error "this should NEVER happen ($opt)";;               # sanity test for previous patterns
    esac
  done
  shift # $EOL
fi

# Do something cool with "$@"... \o/

Примечание: Я знаю ... Аргумент с двоичный образец0x01030307 может нарушить логику. Но если кто-то передает такой аргумент в командной строке, он этого заслуживает.

Хороший умный способ сделать это. Я использую его с этого момента, пока не будет обнаружен лучший способ или, возможно, ошибка ;-)

Enissay 03.01.2021 03:37

Я написал сценарий, который может легко помочь с анализом аргументов командной строки - https://github.com/unfor19/bargs

Примеры

$ bash example.sh -n Willy --gender male -a 99
Name:      Willy
Age:       99
Gender:    male
Location:  chocolate-factory
$ bash example.sh -n Meir --gender male
[ERROR] Required argument: age

Usage: bash example.sh -n Willy --gender male -a 99

--person_name  |  -n  [Willy]              What is your name?
--age          |  -a  [Required]
--gender       |  -g  [Required]
--location     |  -l  [chocolate-factory]  insert your location
$ bash example.sh -h

Usage: bash example.sh -n Willy --gender male -a 99
--person_name  |  -n  [Willy]              What is your name?
--age          |  -a  [Required]
--gender       |  -g  [Required]
--location     |  -l  [chocolate-factory]  insert your location

Еще один вариант парсера (генератора)

https://github.com/ko1nksm/getoptions

варианты - это новый синтаксический анализатор опций (генератор), написанный на POSIX-совместимом сценарии оболочки и выпущенный в августе 2020 года. Он предназначен для тех, кто хочет поддерживать стандартный синтаксис опций в сценариях оболочки без башизмов. Поддерживаемые синтаксисы: -a, +a, -abc, -vvv, -p VALUE, -pVALUE, --flag, --no-flag, --param VALUE, --param=VALUE, --option[=VALUE], --no-option--.

Он поддерживает подкоманды, проверку, сокращенные параметры и автоматическое создание справки. и работает с большинством ОС (Linux, macOS, BSD, Windows и т. д.) и всеми оболочками POSIX (dash, bash, ksh, zsh и т. д.).

#!/bin/sh

. ./lib/getoptions.sh
. ./lib/getoptions_help.sh

parser_definition() {
  setup   REST help:usage -- "Usage: ${2##*/} [options] [arguments]" ''
  flag    FLAG    -f --flag                      -- "--flag option"
  param   PARAM   -p --param                     -- "--param option"
  option  OPTION  -o --option on:"default"  -- "--option option"
  disp    :usage  -h --help
  disp    VERSION    --version
}

eval "$(getoptions parser_definition parse "$0")"
parse "$@"
eval "set -- $REST"

echo "FLAG: $FLAG"
echo "PARAM: $PARAM"
echo "OPTION: $OPTION"
printf ': %s\n' "$@" # Rest arguments

Это также генератор синтаксического анализа параметров, генерирующий следующий код синтаксического анализа параметров.

FLAG=''
PARAM=''
OPTION=''
REST=''
parse() {
  OPTIND=$(($#+1))
  while OPTARG= && [ $# -gt 0 ]; do
    case $1 in
      --?*=*) OPTARG=$1; shift
        eval 'set -- "${OPTARG%%\=*}" "${OPTARG#*\=}"' ${1+'"$@"'}
        ;;
      --no-*) unset OPTARG ;;
      -[po]?*) OPTARG=$1; shift
        eval 'set -- "${OPTARG%"${OPTARG#??}"}" "${OPTARG#??}"' ${1+'"$@"'}
        ;;
      -[!-]?*) OPTARG=$1; shift
        eval 'set -- "${OPTARG%"${OPTARG#??}"}" "-${OPTARG#??}"' ${1+'"$@"'}
        OPTARG= ;;
    esac
    case $1 in
      -f | --flag)
        [ "${OPTARG:-}" ] && OPTARG=${OPTARG#*\=} && set -- noarg "$1" && break
        eval '[ ${OPTARG+x} ] &&:' && OPTARG='1' || OPTARG=''
        FLAG = "$OPTARG"
        ;;
      -p | --param)
        [ $# -le 1 ] && set -- required "$1" && break
        OPTARG=$2
        PARAM = "$OPTARG"
        shift ;;
      -o | --option)
        set -- "$1" "$@"
        [ ${OPTARG+x} ] && {
          case $1 in --no-*) set -- noarg "${1%%\=*}"; break; esac
          [ "${OPTARG:-}" ] && { shift; OPTARG=$2; } || OPTARG='default'
        } || OPTARG=''
        OPTION = "$OPTARG"
        shift ;;
      -h | --help)
        usage
        exit 0 ;;
      --version)
        echo "${VERSION}"
        exit 0 ;;
      --) shift
        while [ $# -gt 0 ]; do
          REST = "${REST} \"\${$((${OPTIND:-0}-$#))}\""
          shift
        done
        break ;;
      [-]?*) set -- unknown "$1" && break ;;
      *) REST = "${REST} \"\${$((${OPTIND:-0}-$#))}\""
    esac
    shift
  done
  [ $# -eq 0 ] && { OPTIND=1; unset OPTARG; return 0; }
  case $1 in
    unknown) set -- "Unrecognized option: $2" "$@" ;;
    noarg) set -- "Does not allow an argument: $2" "$@" ;;
    required) set -- "Requires an argument: $2" "$@" ;;
    pattern:*) set -- "Does not match the pattern (${1#*:}): $2" "$@" ;;
    *) set -- "Validation error ($1): $2" "$@"
  esac
  echo "$1" >&2
  exit 1
}
usage() {
cat<<'GETOPTIONSHERE'
Usage: example.sh [options] [arguments]

  -f, --flag                  --flag option
  -p, --param PARAM           --param option
  -o, --option[=OPTION]       --option option
  -h, --help                  
      --version               
GETOPTIONSHERE
}

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

Используется ./script.sh необязательный-первый-аргумент -key value -key2 value2

#!/bin/sh

a=$(($#-1))
b=$(($#))
while [ $a -gt 0 ]; do
    eval 'key = "$'$a'"; value = "$'$b'"'
    echo "$key => $value"
    b=$(($b-2))
    a=$(($a-2))
done
unset a b key value

[ $(($#%2)) -ne 0 ] && echo "first_arg = "

Конечно, вы можете сделать это слева направо с небольшими изменениями.

Этот фрагмент кода показывает пары ключ => значение и первый аргумент, если он существует.

#!/bin/sh

a=$((1+$#%2))
b=$((1+$a))

[ $(($#%2)) -ne 0 ] && echo "first_arg = "

while [ $a -lt $# ]; do
    eval 'key = "$'$a'"; value = "$'$b'"'
    echo "$key => $value"
    b=$(($b+2))
    a=$(($a+2))
done

unset a b key value

Протестировано 100 000 аргументов, быстро.

Вы также можете перебирать ключ => значение и первый необязательный аргумент слева направо без eval:

#!/bin/sh

a=$(($#%2))
b=0

[ $a -eq 1 ] && echo "first_arg = "

for value; do
    if [ $b -gt $a -a $(($b%2)) -ne $a ]; then
        echo "$key => $value"
    fi
    key = "$value"
    b=$((1+$b))
done

unset a b key value

Другие вопросы по теме