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

1629

Скажем, у меня есть script, который вызывается с помощью этой строки:

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

  • 0
    Для ЗШ-пользователей есть большой предопределённые называемые zparseopts , которые могут сделать: zparseopts -D -E -M -- d=debug -debug=d и имеют как -d и --debug в $debug массива echo $+debug[1] вернет 0 или 1, если один из них используется. Ссылка: zsh.org/mla/users/2011/msg00350.html
Теги:
scripting
arguments
command-line
getopts

32 ответа

2092
Лучший ответ

Метод №1: Использование bash без getopt [s]

Два общих способа передачи аргументов пары "ключ-значение":

Bash Space -s скомпонован (например, --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 Equals -s (например, --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/[^=]*=//'' 'echo "$i" | sed 's/[^=]*=//'' который вызывает два ненужных подпроцесса.

Метод №2: Использование bash с getopt [s]

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

Ограничения 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, которая может быть информативной.

  • 37
    Это правда? Согласно Википедии, существует более новая версия getopt улучшенная GNU, которая включает в себя все функциональные возможности getopts а затем и некоторые. man getopt в Ubuntu 13.04 выводит параметры команды getopt - parse command options (enhanced) в качестве имени, поэтому я предполагаю, что эта расширенная версия теперь является стандартной.
  • 41
    То, что что-то является определенным образом в вашей системе, является очень слабой предпосылкой для того, чтобы основывать предположения о «стандарте».
Показать ещё 17 комментариев
428

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

  • Используйте улучшенный getopt из util-linux или ранее GNU glibc. 1
  • Он работает с getopt_long() функцией C в GNU glibc.
  • Имеет все полезные отличительные особенности (у других их нет):
    • обрабатывает пробелы, кавычки и даже двоичные символы в аргументах 2
    • в конце он может обрабатывать параметры: script.sh -o outFile file1 file2 -v
    • позволяет = -style длинные опции: 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 'Im 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 getopts 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 году)

  • 3
    Спасибо за это. Просто подтвердите из таблицы возможностей на en.wikipedia.org/wiki/Getopts , если вам нужна поддержка длинных опций, и вы не в Solaris, getopt - это то, что нужно.
  • 2
    Я считаю, что единственное предостережение с getopt заключается в том, что его нельзя удобно использовать в сценариях-оболочках, где можно иметь несколько параметров, специфичных для сценария-оболочки, и затем передать параметры не-сценария-оболочки в упакованный исполняемый файл без изменений. Допустим, у меня есть оболочка grep именем mygrep и у меня есть опция --foo специфичная для mygrep , тогда я не могу выполнить mygrep --foo -A 2 , и -A 2 автоматически передается в grep ; Мне нужно сделать mygrep --foo -- -A 2 . Вот моя реализация поверх вашего решения.
Показать ещё 9 комментариев
110

от: 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/[^=]*=//'' 'echo "$i" | sed 's/[^=]*=//'' который вызывает два ненужных подпроцесса.

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

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

Простое использование "getopt" показано в этом мини- script:

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

Мы сказали, что любой из -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
    Использование $* нарушает использование getopt . (Он содержит аргументы с пробелами.) Смотрите мой ответ для правильного использования.
  • 0
    Почему вы хотите сделать это более сложным?
Показать ещё 1 комментарий
83

Рискуя добавить другой пример для игнорирования, вот моя схема.

  • обрабатывает -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
  • 4
    Извините за задержку. В моем сценарии функция handle_argument получает все неопционные аргументы. Вы можете заменить эту строку на что угодно, возможно *) die "unrecognized argument: $1" или собрать аргументы в переменную *) args+="$1"; shift 1;; ,
  • 0
    Удивительно! Я проверил пару ответов, но это единственный, который работал для всех случаев, включая многие позиционные параметры (как до, так и после флагов)
Показать ещё 1 комментарий
81

Более лаконичный способ

script.sh

#!/bin/bash

while [[ "$#" -gt 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
  • 2
    Это то, что я делаю. Нужно while [[ "$#" > 1 ]] если я хочу поддержать завершение строки логическим флагом ./script.sh --debug dev --uglify fast --verbose . Пример: gist.github.com/hfossli/4368aa5a577742c3c9f9266ed214aa58
  • 1
    Я отправил запрос на редактирование. Я только что проверил это, и он отлично работает.
Показать ещё 2 комментария
40

Я опаздываю на этот вопрос на 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
  • 0
    Этот код не может обрабатывать параметры с такими аргументами, как этот: -c1 . И использование = для отделения коротких опций от их аргументов необычно ...
  • 2
    Я столкнулся с двумя проблемами с этим полезным фрагментом кода: 1) «shift» в случае «-c = foo» заканчивает тем, что съел следующий параметр; и 2) 'c' не следует включать в шаблон "[cfr]" для комбинируемых коротких опций.
26

Мой ответ в основном основан на ответе Бруно Броноски, но я как бы размял его две чистые реализации 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

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

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

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

а также:

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

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

ПЛЮСЫ:

  • Позволяет использовать как -arg = значение и -arg значение

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

    • Значение -a или -arg или -arg или -a-r-g или что-то еще
  • Pure bash. Нет необходимости изучать/использовать getopt или getopts

МИНУСЫ:

  • Невозможно объединить args

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

Это единственные плюсы/минусы, которые я могу придумать с головы.

24

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

https://argbash.io

  • 0
    Спасибо за написание argbash, я просто использовал его и обнаружил, что он работает хорошо. В основном я использовал argbash, потому что это генератор кода, поддерживающий более старый bash 3.x, который есть в OS X 10.11 El Capitan. Единственным недостатком является то, что подход с генератором кода означает довольно много кода в вашем основном скрипте, по сравнению с вызовом модуля.
  • 0
    На самом деле вы можете использовать Argbash таким образом, чтобы он создавал специализированную библиотеку синтаксического анализа именно для вас, которую вы могли бы включить в свой скрипт или вы можете поместить в отдельный файл и просто получить исходный код. Я добавил пример, чтобы продемонстрировать это, и я также сделал это более явным в документации.
Показать ещё 2 комментария
13

Развернувшись на отличном ответе от @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
13

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

#!/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
  • 1
    Я прочитал все, и это мой любимый. Я не люблю использовать -a=1 качестве стиля argc. Я предпочитаю ставить сначала основной параметр -options, а затем специальные параметры с одинарным интервалом -o option . Я ищу самый простой-лучший способ чтения argvs.
  • 0
    Это работает очень хорошо, но если вы передадите аргумент опции non:: все следующие опции будут приняты в качестве аргументов. Вы можете проверить эту строку ./myscript -v -d fail -o /fizz/someOtherFile -f ./foo/bar/someFile с вашим собственным сценарием. -d опция не установлена как d:
8

Я даю вам функцию 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 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
  • 0
    Чтобы использовать демо для разбора параметров, которые входят в ваш bash-скрипт, вы просто делаете show_use "$@"
  • 0
    По сути, я обнаружил, что github.com/renatosilva/easyoptions делает то же самое, но немного сложнее, чем эта функция.
8

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

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

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

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

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

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

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

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

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

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
  • 0
    Мне потребовалась минута, чтобы понять, что комментарии в верхней части вашего примера сценария анализируются, чтобы предоставить строку справки по использованию по умолчанию, а также спецификации аргументов. Это блестящее решение, и я сожалею, что за 2 года оно набрало всего 6 голосов. Возможно, этот вопрос слишком затуманен, чтобы люди могли его заметить.
  • 0
    В каком-то смысле ваше решение, безусловно, самое лучшее (кроме @ OleksiiChekulaiev, которое не поддерживает синтаксис «стандартного» параметра). Это связано с тем, что вашему решению требуется, чтобы автор сценария указывал имя каждой опции только один раз . Тот факт, что другие решения требуют, чтобы он был указан 3 раза - при использовании, в шаблоне 'case' и при установке переменной - меня постоянно раздражал. Даже у getopt есть эта проблема. Однако ваш код работает медленно на моей машине - 0.11 с для реализации Bash, 0.28 с для Ruby. По сравнению с 0.02s для явного анализа "while-case".
Показать ещё 1 комментарий
6

Вот как я делаю в функции, чтобы избежать нарушения работы 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
...
}
4

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

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

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

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

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

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

показывает, как долго альтернативные псевдонимы могут использоваться как в Bourne Shell, так и в ksh93.

См. справочную страницу недавней оболочки Борна:

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

4

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

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

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

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

Вы должны решить перед использованием if = для использования по опции или нет. Это значит, что код чист (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
  • 1
    что означает "+ x" на $ {key + x}?
  • 1
    Это проверка, чтобы увидеть, присутствует ли «ключ» или нет. Далее я сбрасываю ключ, и это нарушает внутренний цикл while.
3

Предположим, мы создаем оболочку script с именем 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
  • 2
    Это принимает тот же подход, что и ответ Ноя , но имеет меньше проверок / гарантий безопасности. Это позволяет нам записывать произвольные аргументы в среду сценария, и я уверен, что использование здесь eval может позволить внедрение команд.
2

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

source argparser.sh
parse_args "$@"

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

2

Вот мой подход - с помощью regexp.

  • no 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 действительно

script:

#!/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
  • 0
    Как этот. Может быть, просто добавьте параметр -e в echo с новой строкой.
2

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

project home: 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:"")

наслаждайтесь:)

  • 0
    Я получаю это на Mac OS X: `` `lib / bashopts.sh: строка 138: объявить: -A: недопустимая опция объявить: использование: объявить [-afFirtx] [-p] [имя [= значение] ...] Ошибка в lib / bashopts.sh: 138. 'Declare -x -A bashopts_optprop_name' завершено со статусом 2 Дерево вызовов: 1: lib / controller.sh: 4 source (...) Выход со статусом 1 `` `
  • 0
    Вам нужна Bash версии 4, чтобы использовать это. На Mac версия по умолчанию - 3. Вы можете использовать home brew для установки bash 4.
Показать ещё 1 комментарий
2

Решение, которое сохраняет необработанные аргументы. Включены демонстрации.

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

Использование: ./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

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

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

2

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

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

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

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

может выполняться с достаточно кратким подходом:

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

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

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

# positional remain
node_name=$1
ip_address=$2

- param arg (пробел)

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

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

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

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

Источник

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

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

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

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

# positional remain
node_name=$1
ip_address=$2
1

Расширяя ответ @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[@]}"
1

В этом примере показано, как использовать 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

Наиболее значимыми строками script выше являются следующие:

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

Короткая, точка, читаемая и обрабатывающая практически все (IMHO).

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

1

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

boolean_arg=""
arg_with_value=""

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

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

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

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

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

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

Код с примерами параметров

#!/bin/sh

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

print_usage() {
  echo "Usage:

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

Options:

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

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

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

" >&2
}

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

opt=
while :; do

  if [ $# -le 0 ]; then

    # no parameters remaining -> end option parsing
    break

  elif [ ! "$opt" ]; then

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

  fi

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

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

    -)

      # end of options
      shift
      break
      ;;

    a*|-aaa-0-args)

      echo "Option AAA activated!"
      ;;

    b*|-bbb-1-args)

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

    c*|-ccc-2-args)

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

    h*|\?*|-help)

      print_usage
      exit 0
      ;;

    *)

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

  esac

  if [ "$long_option" = T ]; then

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

  else

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

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

  fi

done

echo "Doing something..."

exit 0

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

Позиция опций с аргументами

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

Несколько опций с аргументами

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

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

Длинные опции также объединены

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

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

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

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

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

Дополнительные аргументы

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

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

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

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

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

Другие примечания

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

1

Вот мое улучшенное решение Bruno Bronosky, используя переменные массивы.

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

#!/bin/bash

echo $@

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

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

$ ./test.sh parameter -s somefile --skip=.c --skip=.obj
parameter -s somefile --skip=.c --skip=.obj
SKIP            = .c .obj
SOFT            = 1
Parameters:
parameter somefile
  • 0
    Вы используете shift для известных аргументов, а не для неизвестных, поэтому оставшиеся $@ будут всеми, кроме первых двух аргументов (в порядке их передачи), что может привести к некоторым ошибкам, если вы попытаетесь использовать $@ позже , Вам не нужно сдвиг для параметров =, так как вы не обрабатываете пробелы и получаете значение с удалением подстроки #*=
  • 0
    На самом деле вы правы, поскольку я создаю переменную PARAMS, мне вообще не нужно использовать shift
1

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

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

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

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

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

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


    echo "SERVER LIST   = ${SERVER_FILE_LIST}"
1

Используйте "аргументы" модуля из bash-modules

Пример:

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

NAME="world"

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

info "Hello, $NAME!"
0

Если вы похожи на меня, и вы просто хотите проанализировать пару входных строк:

#!/bin/bash 
X=$1
echo "The first var is $X"

конец

0

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

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

Ещё вопросы

Сообщество Overcoder
Наверх
Меню