Як я розбираю аргументи командного рядка в Bash?


1920

Скажімо, у мене є сценарій, який викликається за допомогою цього рядка:

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


1
Для zsh-користувачів є чудовий вбудований під назвою zparseopts, який може робити: zparseopts -D -E -M -- d=debug -debug=dІ мають і те, -dі --debugв $debugмасиві echo $+debug[1]поверне 0 або 1, якщо використовується один із них. Посилання: zsh.org/mla/users/2011/msg00350.html
dezza

1
Дійсно хороший підручник: linuxcommand.org/lc3_wss0120.php . Мені особливо подобається приклад "Параметри командного рядка".
Габріель Степлес

Відповіді:


2674

Спосіб №1: Використання bash без getopt [s]

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

Пробіл у розділі пробілу (наприклад, --option argument) (без getopt [s])

Використання demo-space-separated.sh -e conf -s /etc -l /usr/lib /etc/hosts

cat >/tmp/demo-space-separated.sh <<'EOF'
#!/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
EOF

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

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

вихід із копіювання вставленого вище блоку:

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

Bash рівний-розділений (наприклад, --option=argument) (без getopt [s])

Використання demo-equals-separated.sh -e=conf -s=/etc -l=/usr/lib /etc/hosts

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

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

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

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

вихід із копіювання вставленого вище блоку:

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

Щоб краще зрозуміти ${i#*=}пошук "Видалення підрядків" у цьому посібнику . Він функціонально еквівалентний `sed 's/[^=]*=//' <<< "$i"`виклику непотрібного підпроцесу або `echo "$i" | sed 's/[^=]*=//'`виклику двох непотрібних підпроцесів.

Спосіб №2: Використання bash з getopt [s]

з: http://mywiki.wooledge.org/BashFAQ/035#getopts

обмеження getopt (1) (старіші, відносно недавні getoptверсії):

  • не вдається обробити аргументи, які є порожніми рядками
  • не вдається обробити аргументи із вбудованим пробілом

Більш новітні getoptверсії не мають цих обмежень.

Крім того, оболонка POSIX (та інші) пропонують, getoptsщо не має цих обмежень. Я включив спрощений getoptsприклад.

Використання demo-getopts.sh -vf /etc/hosts foo bar

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

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

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

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

shift $((OPTIND-1))

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

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

chmod +x /tmp/demo-getopts.sh

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

вихід із копіювання вставленого вище блоку:

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

Перевагами getoptsє:

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

Недоліком getoptsє те, що він може обробляти лише короткі параметри (а -hне --help) без додаткового коду.

Існує підручник з getopts, який пояснює, що означають усі синтаксиси та змінні. У баші також є help getopts, що може бути інформативним.


44
Це справді правда? Згідно з Вікіпедією, існує новіша версія розширеної програми GNU, getoptяка включає в себе всі функціональні можливості, getoptsа потім і деякі. man getoptна виходах Ubuntu 13.04 getopt - parse command options (enhanced)як назва, тому я припускаю, що ця розширена версія є стандартною.
Livven

47
Те, що щось є певним чином у вашій системі, є дуже слабкою передумовою для обґрунтування припущень "бути стандартними".
szablica

13
@Livven, getoptце не утиліта GNU, це частина util-linux.
Стефан Шазелас

4
Якщо ви користуєтесь -gt 0, видаліть свою shiftпісля esac, shiftдодайте всі значення на 1 і додайте цей випадок: *) break;;ви можете обробити неопціональні аргументи. Напр .: pastebin.com/6DJ57HTc
Ніколас Лакомб

2
Ви не лунаєте –default. У першому прикладі я зауважую, що якщо –defaultце останній аргумент, він не обробляється (вважається непридатним), якщо while [[ $# -gt 1 ]]не встановлено як while [[ $# -gt 0 ]]
колидарт

562

Жодна відповідь не згадує посилений доступ . І відповідь, що голосує на вершині , вводить в оману: він або ігнорує -⁠vfdстилі короткі варіанти (запитує ОП), або варіанти після позиційних аргументів (також вимагає ОП); і вона ігнорує помилки розбору. Замість цього:

  • Використовуйте розширений getoptз util-linux або раніше GNU glibc . 1
  • Він працює з getopt_long()функцією C GNU glibc.
  • Має всі корисні відмінні функції (інші їх не мають):
    • обробляє пробіли, цитуючи символи та навіть двійкові в аргументах 2 (не вдосконалений getoptне може цього зробити)
    • він може обробляти варіанти в кінці: script.sh -o outFile file1 file2 -v( getoptsне робить цього)
    • дозволяє =- довгі параметри стилю: script.sh --outfile=fileOut --infile fileIn(якщо обидва є тривалими, якщо самостійно розбирати)
    • дозволяє комбінувати короткі параметри, наприклад -vfd(реальна робота при самостійному розборі)
    • дозволяє торкатися параметрів-аргументів, наприклад, -oOutfileабо-vfdoOutfile
  • Настільки старий вже 3, що жодна система GNU цього не пропускає (наприклад, у будь-якого Linux).
  • Ви можете перевірити його існування за допомогою: getopt --test→ повернення значення 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

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

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

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

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

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

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

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


4
Дякую за це Щойно підтверджено з таблиці функцій на en.wikipedia.org/wiki/Getopts , якщо вам потрібна підтримка довгих варіантів, і ви не в Solaris, getoptце шлях.
johncip

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

2
@bobpaul Ваше твердження про util-linux також є помилковим та оманливим: на Ubuntu / Debian пакет позначений як "істотний". Як такий, він завжди встановлюється. - Про які дистрибуції ви говорите (де ви говорите, що це потрібно встановити спеціально)?
Роберт Сімер

3
Зверніть увагу: це не працює на Mac принаймні до поточного 10.14.3. Getopt, який доставляє, - це BSD getopt з 1999 року ...
jjj

2
@transang Булеве заперечення повернутого значення. І його побічний ефект: дозволити команді відмовитися (інакше помилка errexit перерве програму з помилкою). - Коментарі в сценарії розповідають більше. В іншому випадку:man bash
Роберт Сімер

144

Більш лаконічний спосіб

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

3
Це я і роблю. Потрібно, while [[ "$#" > 1 ]]якщо я хочу підтримати закінчення рядка булевим прапором ./script.sh --debug dev --uglify fast --verbose. Приклад: gist.github.com/hfossli/4368aa5a577742c3c9f9266ed214aa58
hfossli

12
Оце Так! Просто і чисто! Ось як це я використовую: gist.github.com/hfossli/4368aa5a577742c3c9f9266ed214aa58
hfossli

2
Це набагато приємніше вставляти в кожен сценарій, а не мати справу з джерелом чи не задаватись питанням, звідки у вас функціонал починається.
RealHandy

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

Я використовую це (спасибі!), Але зауважте, що це дозволяє пусте значення аргументу, наприклад ./script.sh -d, не буде генерувати помилку, а просто встановити $deployпорожній рядок.
EM0

137

від: 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/[^=]*=//'`виклику двох непотрібних підпроцесів.


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

3
Це не справляється зі -vfdстильовими комбінованими короткими параметрами.
Роберт Сімер

105

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

Просте використання "getopt" показано в цьому міні-скрипті:

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

Ми сказали, що будь-який із -a, -b, -c або -d буде дозволений, але після цього -c супроводжується аргументом ("c:" говорить, що).

Якщо ми називаємо це "g" і спробуємо це:

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

Ми починаємо з двох аргументів, і "getopt" розбиває варіанти і ставить кожен у свій аргумент. Додано також "-".


4
Використання $*порушено використання getopt. (Тут розміщуються аргументи з пробілами.) Дивіться мою відповідь щодо правильного використання.
Роберт Сімер

Чому б ви хотіли зробити це складніше?
SDsolar

@Matt J, перша частина сценарію (для i) зможе обробляти аргументи з пробілами в них, якщо ви використовуєте "$ i" замість $ i. Схоже, що getopts не в змозі обробляти аргументи з пробілами. Яка була б перевага використання getopt над циклом for i?
thebunnyrules

99

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

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

Сподіваюся, комусь це корисно.

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;;.
Бронсон

Дивовижний! Я перевірив пару відповідей, але це єдиний, який працював у всіх випадках, включаючи безліч позиційних параметрів (як до, так і після прапорів)
Гільерме Гарньє,

2
хороший лаконічний код, але використання -n та жодного іншого аргументу не викликає нескінченного циклу через помилку на shift 2, видаючи shiftдвічі замість shift 2. Запропонував редагувати.
lauksas

42

Я запізнююся на це питання на 4 роки, але хочу повернутись. Раніші відповіді я використовував як вихідну точку, щоб привести в порядок синтаксичний розбір мого старого адмока. Потім я відновив наступний код шаблону. Він обробляє як довгі, так і короткі параметри, використовуючи = або пробіли, розділені аргументами, а також кілька коротких парам, згрупованих разом. Нарешті, він знову вставляє будь-які непарамовні аргументи назад у змінні $ 1, $ 2 ... Я сподіваюся, що це стане в нагоді.

#!/usr/bin/env bash

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

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


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

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


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

Цей код не може обробляти параметри з аргументами , як це: -c1. І використання =відокремлених коротких варіантів від їх аргументів є незвичним ...
Роберт Сімер

2
У мене виникли дві проблеми з цим корисним фрагментом коду: 1) "зсув" у випадку "-c = foo" закінчується поїданням наступного параметра; та 2) 'c' не повинно бути включено до шаблону "[cfr]" для комбінованих коротких варіантів.
sfnd

36

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

https://argbash.io


Дякую, що написав аргбаш, я просто скористався ним і виявив, що він добре працює. Я в основному пішов на аргбаш, тому що це генератор коду, що підтримує старший bash 3.x, знайдений в OS X 10.11 El Capitan. Єдиним недоліком є ​​те, що підхід до генератора коду означає досить багато коду у вашому головному сценарії, порівняно з викликом модуля.
RichVel

Ви можете фактично використовувати Argbash таким чином, що він створює індивідуальну бібліотеку розбору саме для вас, яку ви можете включити до свого сценарію, або ви можете мати його в окремому файлі та просто джерело. Я додав приклад, щоб продемонструвати це, і я зробив це більш чітко в документації.
бульба

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

Питання були вирішені в останній версії argbash: Документація була вдосконалена, введений швидкий старт сценарію argbash-init, і ви навіть можете використовувати argbash в Інтернеті за адресою argbash.io/generate
bubla

29

Моя відповідь багато в чому ґрунтується на відповіді Бруно Броноського , але я на зразок пюре його двох чистих реалізацій 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 = value, так і -arg

  • Працює з будь-яким іменем аргументу, яке ви можете використовувати в bash

    • Значення -a або -arg або --arg або -arg чи будь-що інше
  • Чистий баш. Не потрібно вчитися / використовувати getopt або getopts

Мінуси:

  • Неможливо поєднати аргументи

    • Значення ні -acc. Ви повинні зробити -a -b -c

Це єдині плюси / мінуси, про які я можу придумати голову


15

Я думаю, що цей досить простий у використанні:

#!/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як аргумент стиль. Я вважаю за краще ставити спочатку основний варіант - варіанти, а пізніше спеціальні з одинарним інтервалом -o option. Я шукаю найпростіший спосіб для читання аргументів.
м3нда

Це працює дуже добре, але якщо ви передаєте аргумент не a: варіант, всі наступні параметри будуть прийняті за аргументи. Ви можете перевірити цей рядок за ./myscript -v -d fail -o /fizz/someOtherFile -f ./foo/bar/someFileдопомогою власного сценарію. -d варіант не встановлено як d:
m3nda

15

Розгортаючись на відмінну відповідь від @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

Цей приклад показує, як використовувати getoptта evalі HEREDOCта shiftобробляти короткі та довгі параметри з необхідним значенням та без нього. Крім того, заява про перемикач / випадок є стислим і легким у дотриманні.

#!/usr/bin/env bash

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

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

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

HEREDOC
}  

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

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

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

if (( $verbose > 0 )); then

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

# The rest of your script below

Найбільш значущі рядки вищезазначеного сценарію:

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

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

Короткий, до речі, читабельний і обробляє майже все (IMHO).

Сподіваюся, що хтось допомагає.


1
Це одна з найкращих відповідей.
Містер Polywhirl

11

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

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

Сценарій нижче - це демонстрація робочої копії та вставки. Див. show_useФункцію, щоб зрозуміти, як користуватися parse_params.

Обмеження:

  1. Не підтримує параметри з обмеженим простором ( -d 1)
  2. Назви парамів так втратять тире --any-paramі -anyparamє рівнозначними
  3. eval $(parse_params "$@")необхідно використовувати всередині функції bash (вона не працюватиме в глобальному масштабі)

#!/bin/bash

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

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

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

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

Використовувати демонстрацію для розбору парам, які потрапляють у ваш баш-сценарій, ви просто робитеshow_use "$@"
Олексій Чекулаєв,

В основному я з'ясував, що github.com/renatosilva/easyoptions робить те саме так само, але трохи масивніше, ніж ця функція.
Олексій Чекулаєв

10

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

Знадобилося мені хвилину, щоб зрозуміти, що коментарі у верхній частині вашого сценарію прикладу аналізуються, щоб забезпечити рядок довідки щодо використання за замовчуванням, а також специфікації аргументів. Це геніальне рішення, і мені шкода, що за 2 роки він отримав лише 6 голосів. Можливо, це питання занадто заболочене, щоб люди це помітили.
Метаморфічний

В одному сенсі ваше рішення на сьогодні найкраще (окрім @ OleksiiChekulaiev, яке не підтримує "стандартний" синтаксис варіантів). Це тому, що для вашого рішення потрібно, щоб сценарій сценарію один раз вказав ім'я кожного параметра . Той факт, що інші рішення вимагають, щоб це було визначено 3 рази - у використанні, у шаблоні "case" та в налаштуваннях змінної - постійно мене дратувало. Навіть у getopt є ця проблема. Однак ваш код повільно на моїй машині - 0,11 секунди для реалізації Bash, 0,28 за Ruby. Проти 0,02 для явного розбору "в той час"
Метаморфічний

Я хочу більш швидку версію, можливо, написану на C. Також версія, сумісна з zsh. Можливо, це заслуговує окремого питання ("Чи існує спосіб аналізу аргументів командного рядка в оболонках, схожих на Bash, який приймає стандартний синтаксис з довгими опціями і не вимагає вводити імена варіантів більше одного разу?").
Метаморфічний

10

getopts чудово працює, якщо номер 1 у вас встановлений, а №2 ви маєте намір працювати на одній платформі. OSX та Linux (наприклад) в цьому відношенні поводяться по-різному.

Ось рішення (non 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

8

Ось так я виконую функцію, щоб уникнути того, щоб розриви 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
...
}

8

Розгортаючись на відповідь @ 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[@]}"

6

Існує кілька способів розбору аргументів cmdline (наприклад, GNU getopt (не портативний) проти 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=${@}"

5

Я хотів би запропонувати свою версію розбору варіантів, яка дозволяє:

-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

1
яке значення "+ x" на $ {key + x}?
Лука Даванцо

1
Це тест на перевірку наявності "ключа" чи ні. Далі вниз я скидаю клавішу, і це порушує внутрішню петлю.
galmok

5

Рішення, що зберігає необроблені аргументи. Демо в комплекті.

Ось моє рішення. Він ДУЖЕ гнучкий і, на відміну від інших, не повинен вимагати зовнішніх пакунків і обробляє залишки аргументів чисто.

Використання: ./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 буде оброблятися як єдиний аргумент. Ви могли дещо легко написати додатковий код у шахту, який додає цю функціональність.



3

Зауважте, що це getopt(1)була невелика жива помилка від AT&T.

getopt був створений у 1984 році, але вже похований у 1986 році, оскільки він був непридатний для використання.

Доказом того, що getoptдуже застаріло, є те, що getopt(1)сторінка man все ще згадується "$*"замість того "$@", що вона була додана до оболонки Борна в 1986 році разом із getopts(1)оболонкою, вбудованою для того, щоб мати справу з аргументами з пробілами всередині.

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

Приклад довгих варіантів, взятих із сторінки "Борн Шелл":

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


3

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

Домашня сторінка проекту: https://gitlab.mbedsys.org/mbedsys/bashopts

приклад:

#!/bin/bash -ei

# load the library
. bashopts.sh

# Enable backtrace dusplay on error
trap 'bashopts_exit_handle' ERR

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

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

# Parse arguments
bashopts_parse_args "$@"

# Process argument
bashopts_process_args

допоможе:

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

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

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

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


Я отримую це в Mac OS X: `` `lib / bashopts.sh: рядок 138: заявляти: -A: недійсний параметр заявити: використання: оголосити [-afFirtx] [-p] [ім'я [= значення] ...] Помилка в lib / bashopts.sh: 138. 'оголосити -x -A bashopts_optprop_name' завершено зі статусом 2 Дерево викликів: 1: lib / controller.sh: 4 джерело (...) Вихід із статусом 1 `` `
Джош Вульф

Для цього вам потрібна версія Bash 4. На Mac версії за замовчуванням є 3. Ви можете використовувати домашню заварку для установки bash 4.
Josh Wulf

3

Ось мій підхід - використання regexp.

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

сценарій:

#!/usr/bin/env sh

help_menu() {
  echo "Usage:

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

Options:

  -h, --help
    display this help and exit

  -l, --logfile=FILENAME
    filename

  -d, --debug
    enable debug
  "
}

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

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

Як ця. Можливо, просто додайте параметр -e, щоб повторити новий рядок.
mauron85

3

Припустимо, ми створили сценарій оболонки з назвою, 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

5
Це застосовується так само, як і відповідь Ноя , але має менше перевірок / гарантій безпеки. Це дозволяє нам записувати довільні аргументи в середовище сценарію, і я впевнений, що використання тут eval може дозволити введення команди.
Буде Барнвелл


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

2

Ось 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} в основному є підрядком для видалення першого ---з ключа.


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

Ще одне рішення без getopt [s], POSIX, старий стиль Unix

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

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

Код з прикладними параметрами

#!/bin/sh

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

print_usage() {
  echo "Usage:

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

Options:

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

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

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

" >&2
}

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

opt=
while :; do

  if [ $# -le 0 ]; then

    # no parameters remaining -> end option parsing
    break

  elif [ ! "$opt" ]; then

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

  fi

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

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

    -)

      # end of options
      shift
      break
      ;;

    a*|-aaa-0-args)

      echo "Option AAA activated!"
      ;;

    b*|-bbb-1-args)

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

    c*|-ccc-2-args)

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

    h*|\?*|-help)

      print_usage
      exit 0
      ;;

    *)

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

  esac

  if [ "$long_option" = T ]; then

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

  else

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

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

  fi

done

echo "Doing something..."

exit 0

Для прикладу використання див. Приклади далі.

Позиція варіантів з аргументами

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

Кілька варіантів з аргументами

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

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

Довгі варіанти також поєднуються

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

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

Все це призводить до:

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

Не в цьому рішенні

Необов’язкові аргументи

Варіанти з необов'язковими аргументами повинні бути можливі з невеликою роботою, наприклад, дивлячись вперед, чи є блок без дефісу; Потім користувачеві потрібно буде поставити дефіс перед кожним блоком після блоку з параметром, що має необов'язковий параметр. Можливо, це занадто складно для спілкування з користувачем, тому краще просто вимагати провідного дефісу взагалі.

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

Я особисто віддаю перевагу додатковим варіантам замість необов'язкових аргументів.

Варіанти аргументів, введені знаком рівності

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

Інші примітки

POSIX-сумісний, працює навіть на древніх установках BusyBox мені доводилося мати справу з (з , наприклад cut, headі НЕ getoptsвистачає).

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