Как мне разобрать аргументы командной строки в 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 являются:

  1. Это более портативный, и будет работать в других оболочках, таких как dash,
  2. Он может обрабатывать несколько отдельных опций, таких как -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" разбивает параметры и помещает каждый в свой аргумент. Также добавлено "-".

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

https://argbash.io/

Я на 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 это будет анализировать параметры из командной строки.

  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
# 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) для проверки устаревшего $ *:

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

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

  • нет getopts
  • он обрабатывает блок коротких параметров -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

Предположим, мы создаем сценарий оболочки с именем 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, а примеры - в репозитории.

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