Как мне разобрать аргументы командной строки в 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
?
44 ответа
Способ № 1: Использование bash без getopt[s]
Два распространенных способа передачи аргументов пары ключ-значение:
Bash пробел (например, --option argument
) (без getopt[s])
использование ./myscript.sh -e conf -s /etc -l /usr/lib /etc/hosts
#!/bin/bash
POSITIONAL=()
while [[ $# -gt 0 ]]
do
key="$1"
case $key in
-e|--extension)
EXTENSION="$2"
shift # past argument
shift # past value
;;
-s|--searchpath)
SEARCHPATH="$2"
shift # past argument
shift # past value
;;
-l|--lib)
LIBPATH="$2"
shift # past argument
shift # past value
;;
--default)
DEFAULT=YES
shift # past argument
;;
*) # unknown option
POSITIONAL+=("$1") # 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 $1 ]]; then
echo "Last line of file specified as non-opt/last argument:"
tail -1 "$1"
fi
Bash равно-разделенный (например, --option=argument
) (без getopt[s])
использование ./myscript.sh -e=conf -s=/etc -l=/usr/lib /etc/hosts
#!/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 "Number files in SEARCH PATH with EXTENSION:" $(ls -1 "${SEARCHPATH}"/*."${EXTENSION}" | wc -l)
if [[ -n $1 ]]; then
echo "Last line of file specified as non-opt/last argument:"
tail -1 $1
fi
Чтобы лучше понять ${i#*=}
найдите "Удаление подстроки" в этом руководстве. Функционально эквивалентно `sed 's/[^=]*=//' <<< "$i"`
который вызывает ненужный подпроцесс или `echo "$i" | sed 's/[^=]*=//'`
который вызывает два ненужных подпроцесса.
Способ № 2: Использование bash с getopt[s]
от: http://mywiki.wooledge.org/BashFAQ/035
getopt(1) ограничения (старые, относительно недавние) getopt
версии):
- не может обрабатывать аргументы, которые являются пустыми строками
- не может обрабатывать аргументы со встроенным пробелом
Более свежий getopt
версии не имеют этих ограничений.
Кроме того, оболочка POSIX (и другие) предлагает getopts
который не имеет этих ограничений. Вот упрощенный getopts
пример:
#!/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: $@"
# End of file
Преимущества getopts
являются:
- Это более портативный, и будет работать в других оболочках, таких как
dash
, - Он может обрабатывать несколько отдельных опций, таких как
-vf filename
в обычном режиме Unix, автоматически.
Недостаток getopts
является то, что он может обрабатывать только короткие варианты (-h
не --help
) без дополнительного кода.
Существует учебник getopts, который объясняет, что означает весь синтаксис и переменные. В bash также есть help getopts
, который может быть информативным.
Нет ответа упоминает расширенный Getopt. И самый голосующий ответ вводит в заблуждение: он игнорирует -vfd
стиль короткие параметры (запрашиваемые OP), параметры после позиционных аргументов (также запрашиваемые OP), и он игнорирует ошибки синтаксического анализа. Вместо:
- Используйте улучшенный
getopt
из util-linux или ранее GNU glibc. 1 - Работает с
getopt_long()
функция C в GNU glibc. - Имеет все полезные отличительные особенности (у других их нет):
- обрабатывает пробелы, кавычки и даже двоичные символы в аргументах 2
- он может обрабатывать параметры в конце:
script.sh -o outFile file1 file2 -v
- позволяет
=
Стиль длинных опций:script.sh --outfile=fileOut --infile fileIn
- Он настолько стар, что ему уже 3 года, и ни в одной системе GNU его нет (например, в любом Linux).
- Вы можете проверить его наличие с помощью:
getopt --test
→ вернуть значение 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
# saner programming env: these switches turn some bugs into errors
set -o errexit -o pipefail -o noclobber -o nounset
! 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
# -use ! and PIPESTATUS to get exit code with errexit set
# -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"
1 расширенный getopt доступен на большинстве "bash-систем", включая Cygwin; на OS X попробуйте установить bnu gnu-getopt или sudo port install getopt
2 POSIX exec()
соглашения не имеют надежного способа передачи двоичного NULL в аргументах командной строки; эти байты преждевременно заканчивают спор
3 первая версия, выпущенная в 1997 году или раньше (я отслеживал ее только в 1997 году)
Более лаконичный способ
script.sh
#!/bin/bash
while [[ "$#" > 0 ]]; do case $1 in
-d|--deploy) deploy="$2"; shift;;
-u|--uglify) uglify=1;;
*) echo "Unknown parameter passed: $1"; exit 1;;
esac; shift; done
echo "Should deploy? $deploy"
echo "Should uglify? $uglify"
Использование:
./script.sh -d dev -u
# OR:
./script.sh --deploy dev --uglify
От: 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/[^=]*=//'`
который вызывает два ненужных подпроцесса.
На риск добавления еще одного примера, чтобы проигнорировать, вот моя схема.
- ручки
-n arg
а также--name=arg
- позволяет аргументы в конце
- показывает вменяемые ошибки, если что-то написано с ошибкой
- совместимый, не использует bashisms
- читаемый, не требует поддержания состояния в цикле
Надеюсь, это кому-нибудь пригодится.
while [ "$#" -gt 0 ]; do
case "$1" in
-n) name="$2"; shift 2;;
-p) pidfile="$2"; shift 2;;
-l) logfile="$2"; shift 2;;
--name=*) name="${1#*=}"; shift 1;;
--pidfile=*) pidfile="${1#*=}"; shift 1;;
--logfile=*) logfile="${1#*=}"; shift 1;;
--name|--pidfile|--logfile) echo "$1 requires an argument" >&2; exit 1;;
-*) echo "unknown option: $1" >&2; exit 1;;
*) handle_argument "$1"; shift 1;;
esac
done
getopt()
/getopts()
хороший вариант Украдено отсюда:
Простое использование getopt показано в этом мини-скрипте:
#!/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
Мы сказали, что любой из -a, -b, -c или -d будет разрешен, но за -c следует аргумент ("c:" говорит это).
Если мы назовем это "g" и попробуем это:
bash-2.05a$ ./g -abc foo
Before getopt
-abc
foo
After getopt
-->-a
-->-b
-->-c
-->foo
-->--
Мы начинаем с двух аргументов, и "getopt" разбивает параметры и помещает каждый в свой аргумент. Также добавлено "-".
Я на 4 года опоздал на этот вопрос, но хочу вернуть. Я использовал предыдущие ответы в качестве отправной точки, чтобы привести в порядок мой старый анализ параметров 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
Мой ответ в значительной степени основан на ответе Бруно Броноски, но я как бы скомбинировал его две чистые реализации bash в одну, которую я использую довольно часто.
# As long as there is at least one more argument, keep looping
while [[ $# -gt 0 ]]; do
key="$1"
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="$1"
;;
# 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 значение
Работает с любым именем arg, которое вы можете использовать в bash
- Значение -a или -arg или --arg или -arg или что-то еще
Чистая Баш. Нет необходимости изучать / использовать getopt или getopts
МИНУСЫ:
Не могу объединить аргументы
- Значение нет-abc. Вы должны сделать -a -b -c
Это единственные плюсы / минусы, которые я могу придумать с головы до головы
Этот пример показывает, как использовать 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, don't 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
Коротко, по сути, читабельно и обрабатывает практически все (ИМХО).
Надеюсь, это поможет кому-то.
Меня вдохновил относительно простой ответ @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
может нарушить логику. Но если кто-то передает такой аргумент в командной строке, он этого заслуживает.
Расширяя ответ @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[@]}"
Расширяя превосходный ответ @guneysus, здесь есть твик, который позволяет пользователю использовать любой синтаксис, который он предпочитает, например
command -x=myfilename.ext --another_switch
против
command -x myfilename.ext --another_switch
То есть, равные могут быть заменены пробелами.
Эта "нечеткая интерпретация" может не понравиться, но если вы создаете сценарии, которые взаимозаменяемы с другими утилитами (как в случае с моей, которая должна работать с ffmpeg), гибкость полезна.
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
Я думаю, что этот достаточно прост в использовании:
#!/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
Я написал еще один вариант парсера (и генератора).
https://github.com/ko1nksm/getoptions
Поддерживаемые синтаксисы: -a
, +a
, -abc
, -vvv
, -p value
, -pvalue
, --param value
, --param=value
, --
.
Он портативен, совместим с POSIX и не требует каких-либо проблем. использует только встроенные команды оболочки иcat
за помощью. Поэтому он работает с большинством ОС (Linux, macOS, BSD и т. Д.) И всеми оболочками POSIX (dash, bash, ksh, zsh и т. Д.). Автоматическое создание справки доступно и очень просто в использовании.
#!/bin/sh
. ./getoptions.sh
parser_definition() {
setup REST -- "Usage: ${2##*/} [options] [arguments]" ''
flag FLAG -f --flag -- "--flag option"
param PARAM -p --param -- "--param option"
option OPTION -o --option default:"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
eval "$(getoptions_help parser_definition usage "$0")"
usage
Я даю вам функцию parse_params
это будет анализировать параметры из командной строки.
- Это чисто решение Bash, никаких дополнительных утилит.
- Не загрязняет глобальные рамки.
- Без усилий возвращает вам простые в использовании переменные, на которых вы могли бы построить дополнительную логику.
- Количество тире перед параграфами не имеет значения (
--all
равняется-all
равняетсяall=all
)
Сценарий ниже является рабочей демонстрацией копирования-вставки. Увидеть show_use
функция, чтобы понять, как использовать parse_params
,
Ограничения:
- Не поддерживает разделенные пробелами параметры (
-d 1
) - Имена параметров потеряют тире так
--any-param
а также-anyparam
эквивалентны eval $(parse_params "$@")
должен использоваться внутри функции bash (она не будет работать в глобальной области видимости)
#!/bin/bash
# Universal Bash parameter parsing
# Parses equal sign separated params into local variables (--name=bob creates variable $name=="bob")
# 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)
# Parses un-named params into ${ARGV[*]} array
# Additionally puts all named params raw into ${ARGN[*]} array
# Additionally puts all standalone "option" params raw into ${ARGO[*]} array
# @author Oleksii Chekulaiev
# @version v1.4 (Jun-26-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 [[ "$1" != "" ]]; do
# Escape asterisk to prevent bash asterisk expansion
_escaped=${1/\*/\'\"*\"\'}
# If equals delimited named parameter
if [[ "$1" =~ ^..*=..* ]]; then
# Add to named parameters array
echo "ARGN+=('$_escaped');"
# key is part before first =
local _key=$(echo "$1" | cut -d = -f 1)
# 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
if [[ "$_key" == "" ]]; then
shift
continue
fi
# 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 [[ "$1" =~ ^\-. ]]; then
# remove dashes
local _key=${1//\-}
# skip when key is empty
if [[ "$_key" == "" ]]; then
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
getopts прекрасно работает, если #1 у вас установлен и #2 вы собираетесь запустить его на той же платформе. OSX и Linux (например) ведут себя по-разному в этом отношении.
Вот (не getopts) решение, которое поддерживает флаги equals, non-equals и boolean. Например, вы можете запустить свой скрипт следующим образом:
./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
EasyOptions не требует разбора:
## Options:
## --verbose, -v Verbose mode
## --output=FILE Output filename
source easyoptions || exit
if test -n "${verbose}"; then
echo "output file is ${output}"
echo "${arguments[@]}"
fi
Я хочу представить свой проект: https://github.com/flyingangel/argparser
source argparser.sh
parse_args "$@"
Просто как тот. Окружение будет заполнено переменными с тем же именем, что и аргументы.
Основываясь на других ответах здесь, эта моя версия:
#!/bin/bash
set -e
function parse() {
for arg in "$@"; do # transform long options to short ones
shift
case "$arg" in
"--name") set -- "$@" "-n" ;;
"--verbose") set -- "$@" "-v" ;;
*) set -- "$@" "$arg"
esac
done
while getopts "n:v" optname
do
case "$optname" in
"n") name=${OPTARG} ;;
"v") verbose=true ;;
esac
done
shift "$((OPTIND-1))" # shift out all the already processed options
}
parse "$@"
echo "hello $name"
if [ ! -z $verbose ]; then echo 'nice to meet you!'; fi
Использование:
$ ./parse.sh
hello
$ ./parse.sh -n YOUR_NAME
hello YOUR_NAME
$ ./parse.sh -n YOUR_NAME -v
hello YOUR_NAME
nice to meet you!
$ ./parse.sh -v -n YOUR_NAME
hello YOUR_NAME
nice to meet you!
$ ./parse.sh -v
hello
nice to meet you!
Вот как я это делаю в функции, чтобы не нарушать одновременное выполнение 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
...
}
Я хотел бы предложить свою версию анализа параметров, которая позволяет следующее:
-s p1
--stage p1
-w somefolder
--workfolder somefolder
-sw p1 somefolder
-e=hello
Также позволяет это (может быть нежелательным):
-s--workfolder p1 somefolder
-se=hello p1
-swe=hello p1 somefolder
Вы должны решить перед использованием, будет ли = использоваться с опцией или нет. Это делается для того, чтобы сохранить код чистым (ish).
while [[ $# > 0 ]]
do
key="$1"
while [[ ${key+x} ]]
do
case $key in
-s*|--stage)
STAGE="$2"
shift # option has parameter
;;
-w*|--workfolder)
workfolder="$2"
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
Есть несколько способов разобрать аргументы cmdline (например, GNU getopt (не переносимый) vs BSD (OSX) getopt vs getopts) - все это проблематично. Это решение
- портативный!
- не имеет никаких зависимостей, полагается только на встроенные модули bash
- позволяет использовать как короткие, так и длинные варианты
- обрабатывает пробелы между опцией и аргументом, но также может использовать
=
разделитель - поддерживает объединенный стиль коротких опций
-vxf
- обрабатывает опцию с необязательными аргументами (см. пример) и
- не требует раздувания кода по сравнению с альтернативами для того же набора функций. Т.е. лаконично, а значит проще в обслуживании
Примеры: любой из
# 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=${@}"
Решение, которое сохраняет необработанные аргументы. Демоверсии включены.
Вот мое решение. Он ОЧЕНЬ гибок и, в отличие от других, не требует внешних пакетов и аккуратно обрабатывает оставшиеся аргументы.
Использование это: ./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=$1
for flag in $validflags
do
sflag="-"$flag
if [ "$argval" == "$sflag" ]
then
declare $flag=$2
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=$1
# argval=$(echo $@ | cut -d ' ' -f$count)
for flag in $validflags
do
sflag="-"$flag
if [ "$argval" == "$sflag" ]
then
declare $flag=$2
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: $1 arg2: $2
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=$1
match=0
if [ "${argval:0:1}" == "-" ]
then
for flag in $validflags
do
sflag="-"$flag
if [ "$argval" == "$sflag" ]
then
declare $flag=$2
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(1)
была недолгая ошибка от AT & T.
getopt был создан в 1984 году, но уже похоронен в 1986 году, потому что он не был действительно применим.
Доказательство того факта, что getopt
очень устарел в том, что getopt(1)
страница руководства все еще упоминает "$*"
вместо "$@"
, который был добавлен в Bourne Shell в 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) для проверки устаревшего $ *:
Вот мой подход - использование регулярных выражений.
- нет getopts
- он обрабатывает блок коротких параметров
-qwerty
- он обрабатывает короткие параметры
-q -w -e
- он обрабатывает длинные варианты
--qwerty
- Вы можете передать атрибут короткой или длинной опции (если вы используете блок коротких опций, атрибут присоединяется к последней опции)
- Вы можете использовать пробелы или
=
предоставлять атрибуты, но атрибуты совпадают, пока не встретятся дефис + пробел "разделитель", поэтому в--q=qwe ty
qwe 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
Предположим, мы создаем сценарий оболочки с именем test_args.sh
следующим образом
#!/bin/sh
until [ $# -eq 0 ]
do
name=${1:1}; shift;
if [[ -z "$1" || $1 == -* ]] ; then eval "export $name=true"; else eval "export $name=$1"; 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
Вот getopts, который выполняет синтаксический анализ с минимальным количеством кода и позволяет вам определить, что вы хотите извлечь в одном случае, используя eval с подстрокой.
В основном eval "local key='val'"
function myrsync() {
local backup=("${@}") args=(); while [[ $# -gt 0 ]]; do k="$1";
case "$k" in
---sourceuser|---sourceurl|---targetuser|---targeturl|---file|---exclude|---include)
eval "local ${k:3}='${2}'"; shift; shift # Past two arguments
;;
*) # Unknown option
args+=("$1"); shift; # Past argument only
;;
esac
done; set -- "${backup[@]}" # Restore $@
echo "${sourceurl}"
}
Объявляет переменные как локальные, а не глобальные, как в большинстве ответов здесь.
Называется как:
myrsync ---sourceurl http://abc.def.g ---sourceuser myuser ...
${K:3} - это, по сути, подстрока для удаления первого ---
от ключа.
Я написал Bash Helper, чтобы написать хороший инструмент 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:"")
наслаждаться:)
Хотел поделиться, что сделал для разбора вариантов. Некоторые из моих потребностей не были удовлетворены (например, управление подопциями, аргументы переменных, дополнительные аргументы и т. Д.) В ответах здесь, поэтому мне пришлось придумать следующее: https://github.com/MihirLuthra/bash_option_parser
Допустим, у нас есть команда с именем fruit
со следующим использованием:
fruit ...
[-e|—-eat|—-chew]
[-c|--cut <how> <why>]
<command> [<args>]
-e
не принимает аргументов-c
принимает два аргумента, т.е. как вырезать и зачем вырезать<command>
для подопций, таких как apple
, orange
и т.д. (аналогично git
который имеет подопции commit
, push
так далее.)
Итак, чтобы разобрать его:
source ./option_parser
parse_options \
',' 'OPTIONS' 'ARG_CNT' 'ARGS' 'self' '0' ';' '--' \
\
'-e' , '—-eat' , '—-chew' '0' \
'-c' , '—-cut' , '1 1' \
'apple' 'S' \
'orange' 'S' \
\
';' "$@"
Теперь, если возникла ошибка использования, ее можно распечатать с помощью option_parser_error_msg
следующее:
retval=$?
if [ $retval -ne 0 ]; then
option_parser_error_msg "$retval" 'OPTIONS'
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
Для подопции, например apple
чье использование выглядит так:
fruit apple ...
[—-eat-apple|—-chew-apple]
[-p|—-peel <how>]
Мы можем проверить, было ли оно передано, и проанализировать его следующим образом:
if [ -n "${OPTION[apple]}" ]
then
shift_count=${OPTION[apple]}
parse_options \
',' 'OPTIONS_apple' 'ARG_CNT_apple' 'ARGS_apple' \
'self' "$shift_count" ';' '--' \
\
'—-eat-apple' , '—-chew-apple' '0' \
'-p' , '—-peel' '1' \
\
';' "$@"
fi
Парсинг подопции осуществляется путем передачи $shift_count
к parse_options
что заставляет его начать синтаксический анализ после сдвига аргументов для достижения аргументов подопции.
Подробное описание приведено в файле readme, а примеры - в репозитории.