Скажем, у меня есть сценарий, который вызывается с такой строкой:
./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?
Действительно хороший учебник: linuxcommand.org/lc3_wss0120.php. Мне особенно нравится пример «Параметры командной строки».
Я создал сценарий, который сделает это за вас, он называется - github.com/unfor19/bargs
См. Также Предоставить сценарию bash возможность принимать флаги, например, команду? для подробного, специального, длинного и короткого парсера опций. Он не пытается обрабатывать аргументы параметра, прикрепленные к коротким параметрам, или длинные параметры с =, отделяющим имя параметра от значения параметра (в обоих случаях он просто предполагает, что значение параметра находится в следующем аргументе). Он также не обрабатывает кластеризацию коротких опций - вопрос не нужен.
, , etc., 2) flags with getopts and ${OPTARG}, 3) looping over all parameters ($@), and 4) looping over all parameters using $#, , and the shift operator.





getopt() / getopts() - хороший вариант. Скопировано с здесь:
The simple use of "getopt" is shown in this mini-script:
#!/bin/bash
echo "Before getopt"
for i
do
echo $i
done
args=`getopt abc:d $*`
set -- $args
echo "After getopt"
for i
do
echo "-->$i"
done
What we have said is that any of -a, -b, -c or -d will be allowed, but that -c is followed by an argument (the "c:" says that).
If we call this "g" and try it out:
bash-2.05a$ ./g -abc foo
Before getopt
-abc
foo
After getopt
-->-a
-->-b
-->-c
-->foo
-->--
We start with two arguments, and "getopt" breaks apart the options and puts each in its own argument. It also added "--".
Использование $* нарушает использование getopt. (Аргументы заключаются в пробелы.) См. мой ответ для правильного использования.
Зачем вам усложнять?
@Matt J, первая часть скрипта (для i) сможет обрабатывать аргументы с пробелами в них, если вы используете «$ i» вместо $ i. Похоже, что getopts не может обрабатывать аргументы с пробелами. В чем преимущество использования getopt перед циклом for i?
Я думаю, что это достаточно просто использовать:
#!/bin/bash
#
readopt='getopts $opts opt;rc=$?;[ $rc$opt == 0? ]&&exit 1;[ $rc == 0 ]||{ shift $[OPTIND-1];false; }'
opts=vfdo:
# Enumerating options
while eval $readopt
do
echo OPT:$opt ${OPTARG+OPTARG:$OPTARG}
done
# Enumerating arguments
for arg
do
echo ARG:$arg
done
Пример вызова:
./myscript -v -do /fizz/someOtherFile -f ./foo/bar/someFile
OPT:v
OPT:d
OPT:o OPTARG:/fizz/someOtherFile
OPT:f
ARG:./foo/bar/someFile
Я читаю все, и этот - мой любимый. Мне не нравится использовать -a=1 в качестве стиля argc. Я предпочитаю ставить сначала основную опцию -options, а затем специальные с одинарным интервалом -o option. Я ищу самый простой способ читать argvs.
Он работает очень хорошо, но если вы передадите аргумент параметру, отличному от a:, все следующие параметры будут приняты в качестве аргументов. Вы можете проверить эту строку ./myscript -v -d fail -o /fizz/someOtherFile -f ./foo/bar/someFile своим собственным скриптом. Параметр -d не установлен как d:
От digitalpeer.com с небольшими изменениями:
Использование myscript.sh -p=my_prefix -s=dirname -l=libname
#!/bin/bash
for i in "$@"
do
case $i in
-p=*|--prefix=*)
PREFIX = "${i#*=}"
;;
-s=*|--searchpath=*)
SEARCHPATH = "${i#*=}"
;;
-l=*|--lib=*)
DIR = "${i#*=}"
;;
--default)
DEFAULT=YES
;;
*)
# unknown option
;;
esac
done
echo PREFIX = ${PREFIX}
echo SEARCH PATH = ${SEARCHPATH}
echo DIRS = ${DIR}
echo DEFAULT = ${DEFAULT}
Чтобы лучше понять ${i#*=}, найдите «Удаление подстроки» в это руководство. Функционально он эквивалентен `sed 's/[^=]*=//' <<< "$i"`, который вызывает ненужный подпроцесс, или `echo "$i" | sed 's/[^=]*=//'`, который вызывает ненужные подпроцессы два.
Аккуратный! Хотя это не сработает для аргументов, разделенных пробелами, как mount -t tempfs .... Возможно, это можно исправить с помощью чего-то вроде while [ $# -ge 1 ]; do param=; shift; case $param in; -p) prefix=; shift;; и т. д.
Это не подходит для комбинированных коротких вариантов в стиле -vfd.
Bash с разделением пробелами (например, --option argument)
cat >/tmp/demo-space-separated.sh <<'EOF'
#!/bin/bash
POSITIONAL=()
while [[ $# -gt 0 ]]
do
key = ""
case $key in
-e|--extension)
EXTENSION = ""
shift # past argument
shift # past value
;;
-s|--searchpath)
SEARCHPATH = ""
shift # past argument
shift # past value
;;
-l|--lib)
LIBPATH = ""
shift # past argument
shift # past value
;;
--default)
DEFAULT=YES
shift # past argument
;;
*) # unknown option
POSITIONAL+=("") # save it in an array for later
shift # past argument
;;
esac
done
set -- "${POSITIONAL[@]}" # restore positional parameters
echo "FILE EXTENSION = ${EXTENSION}"
echo "SEARCH PATH = ${SEARCHPATH}"
echo "LIBRARY PATH = ${LIBPATH}"
echo "DEFAULT = ${DEFAULT}"
echo "Number files in SEARCH PATH with EXTENSION:" $(ls -1 "${SEARCHPATH}"/*."${EXTENSION}" | wc -l)
if [[ -n ]]; then
echo "Last line of file specified as non-opt/last argument:"
tail -1 ""
fi
EOF
chmod +x /tmp/demo-space-separated.sh
/tmp/demo-space-separated.sh -e conf -s /etc -l /usr/lib /etc/hosts
Результат копирования-вставки блока выше:
FILE EXTENSION = conf
SEARCH PATH = /etc
LIBRARY PATH = /usr/lib
DEFAULT =
Number files in SEARCH PATH with EXTENSION: 14
Last line of file specified as non-opt/last argument:
#93.184.216.34 example.com
Использование:
demo-space-separated.sh -e conf -s /etc -l /usr/lib /etc/hosts
Bash с разделением на равенство (например, --option=argument)
cat >/tmp/demo-equals-separated.sh <<'EOF'
#!/bin/bash
for i in "$@"
do
case $i in
-e=*|--extension=*)
EXTENSION = "${i#*=}"
shift # past argument=value
;;
-s=*|--searchpath=*)
SEARCHPATH = "${i#*=}"
shift # past argument=value
;;
-l=*|--lib=*)
LIBPATH = "${i#*=}"
shift # past argument=value
;;
--default)
DEFAULT=YES
shift # past argument with no value
;;
*)
# unknown option
;;
esac
done
echo "FILE EXTENSION = ${EXTENSION}"
echo "SEARCH PATH = ${SEARCHPATH}"
echo "LIBRARY PATH = ${LIBPATH}"
echo "DEFAULT = ${DEFAULT}"
echo "Number files in SEARCH PATH with EXTENSION:" $(ls -1 "${SEARCHPATH}"/*."${EXTENSION}" | wc -l)
if [[ -n ]]; then
echo "Last line of file specified as non-opt/last argument:"
tail -1
fi
EOF
chmod +x /tmp/demo-equals-separated.sh
/tmp/demo-equals-separated.sh -e=conf -s=/etc -l=/usr/lib /etc/hosts
Результат копирования-вставки блока выше:
FILE EXTENSION = conf
SEARCH PATH = /etc
LIBRARY PATH = /usr/lib
DEFAULT =
Number files in SEARCH PATH with EXTENSION: 14
Last line of file specified as non-opt/last argument:
#93.184.216.34 example.com
Использование:
demo-equals-separated.sh -e=conf -s=/etc -l=/usr/lib /etc/hosts
Чтобы лучше понять ${i#*=}, найдите «Удаление подстроки» в это руководство. Функционально он эквивалентен `sed 's/[^=]*=//' <<< "$i"`, который вызывает ненужный подпроцесс, или `echo "$i" | sed 's/[^=]*=//'`, который вызывает ненужные подпроцессы два.
Использование bash с getopt [s]
getopt (1) ограничения (более старые, относительно недавние версии getopt):
Более поздние версии getopt не имеют этих ограничений.
От: http://mywiki.wooledge.org/BashFAQ/035#getopts
Кроме того, оболочка POSIX (и другие) предлагает getopts, который не имеет этих ограничений. Я включил упрощенный пример getopts.
cat >/tmp/demo-getopts.sh <<'EOF'
#!/bin/sh
# A POSIX variable
OPTIND=1 # Reset in case getopts has been used previously in the shell.
# Initialize our own variables:
output_file = ""
verbose=0
while getopts "h?vf:" opt; do
case "$opt" in
h|\?)
show_help
exit 0
;;
v) verbose=1
;;
f) output_file=$OPTARG
;;
esac
done
shift $((OPTIND-1))
[ "${1:-}" = "--" ] && shift
echo "verbose=$verbose, output_file='$output_file', Leftovers: $@"
EOF
chmod +x /tmp/demo-getopts.sh
/tmp/demo-getopts.sh -vf /etc/hosts foo bar
Результат копирования-вставки блока выше:
verbose=1, output_file='/etc/hosts', Leftovers: foo bar
Использование:
demo-getopts.sh -vf /etc/hosts foo bar
Преимущества getopts:
dash.-vf filename, типичным для Unix способом.Недостатком getopts является то, что он может обрабатывать только короткие варианты (-h, но не --help) без дополнительного кода.
Существует руководство по getopts, который объясняет, что означают весь синтаксис и переменные. В bash также есть help getopts, который может быть информативным.
Это правда? Согласно Википедия, существует более новая расширенная версия getopt GNU, которая включает в себя все функции getopts, а затем некоторые. man getopt в Ubuntu 13.04 выводит getopt - parse command options (enhanced) в качестве имени, поэтому я предполагаю, что эта расширенная версия сейчас является стандартной.
То, что в вашей системе что-то работает определенным образом, является очень слабой предпосылкой для того, чтобы основывать предположения о "стандартизации".
@Livven, getopt не является утилитой GNU, это часть util-linux.
Я использовал это с небольшой модификацией, чтобы добавить все нераспознанные параметры в массив, который затем становится нашим новым $ @ gist.github.com/rciorba/514fd75f4a6471d44d71
Если вы используете -gt 0, удалите shift после esac, увеличьте все shift на 1 и добавьте этот случай: *) break;;, вы можете обрабатывать необязательные аргументы. Пример: pastebin.com/6DJ57HTc
Хотя самим анализировать аргументы с помощью цикла while и case - это правильный путь, пожалуйста, не используйте пример кода этого ответа. У него есть неприятные проблемы, такие как молчаливое игнорирование неизвестных или искаженных параметров и отсутствие поддержки конечных позиционных параметров (т.е. вещей после -)
Просто хотел отстаивать рекомендованное решение. Обманувшись с несколькими вариантами, это самый безболезненный и простой.
Вы не повторяете –default. В первом примере я заметил, что если –default является последним аргументом, он не обрабатывается (считается неопциональным), если while [[ $# -gt 1 ]] не установлен как while [[ $# -gt 0 ]].
Я наконец исправил проблему в примере «Straight Bash Space Separated» с -gt 0 и -gt 1, как описано @ NicolasMongrain-Lacombe и @kolydart
getopts "h?vf:" должен быть getopts "hvf:" без вопросительного знака. Непризнанные аргументы сохраняются как ? в $opt. Цитата из man builtins: “The colon and question mark characters may not be used as option characters.”
Если вы работаете в «строгом режиме» bash (#!/bin/bash -u или set -eu), восстанавливайте позиционные параметры только в том случае, если массив POSITIONAL не пуст, чтобы избежать ошибки unbound variable, например: if [ ${#POSITIONAL[@]} -gt 0 ]; then set -- "${POSITIONAL[@]}" ; fi # restore positional parameters
Первый пример, похоже, не обрабатывает позиционные аргументы с пробелами: test.sh "a b" будет иметь , установленный на «a», а , установленный на «b».
Как я могу обработать часть аргументов, распознаваемых текущим скриптом, и передать остальное другому скрипту с помощью $@? $@ пуст после анализа аргументов, как показано в ответе.
@ MartynasJusevičius команды shift принимают их или $@, поэтому ограничьте количество смен.
Значит, мне нужно было бы собирать неизвестные параметры в свой собственный массив вместо $@? Я думал, что $POSITIONAL делает что-то подобное, но похоже, что он хранит только ключи, а не значения. Какая у этого цель?
Собственно, если синтаксис +=("") добавить к массиву, у меня это не сработает. Когда я печатаю $POSITIONAL, я получаю только самый первый вариант. Edit: nevermind, ${POSITIONAL[@]} работает :) Спасибо.
при использовании «сдвига» в цикле я бы посоветовал сделать что-то вроде: shift || exit 1 Это потому, что, если сдвиг работает неправильно и по какой-то причине не удается сдвинуть аргументы, вы можете попасть в бесконечный цикл. Таким образом, вместо этого, если у shift есть возвращаемое значение, отличное от 0, вы можете безопасно выйти и сообщить об ошибке.
Bash Equals-Separated (e.g., --option=argument) (without getopt[s]), какие изменения мне нужно будет сделать для -x y вместо -x=y?
Это очень хороший способ сделать это (без getopts), если вы знаете, что можете использовать bash. Для меня это почти 99% времени. Я использую это с большинством моих скриптов среднего уровня в моей собственной среде или как вспомогательные скрипты в моем собственном программном обеспечении.
В методе Bash Equals-Separated (e.g., --option=argument) (without getopt[s]) зачем нужен встроенный shift? Разве цикл for не должен автоматически перебирать каждый параметр? Если вы использовали предоставленный пример как есть, но просто удалили команды shift, не было бы лучше, если бы вы не отбрасывали свои входные данные?
@TyrelKostyk, если вы действительно попробуете свое предложение, вы увидите, что сдвиг - это то, что заставляет указывать на первый неопциональный аргумент /etc/hosts. Если вы не переключитесь, вам придется увеличить свой собственный ((counter++)) в цикле, а затем вызвать ${!counter}, чтобы получить «переменную переменную». Если тебе так лучше, сделай это. Но я думаю, что это ненужная сложность, чтобы избежать отбрасывание входов, которые хранятся как именованные переменные.
@BrunoBronosky Спасибо за ответ! Имеет смысл. Я обнаружил кое-что из своего замешательства: я применял ваш ответ в форме функции "parse_inputs". И я обнаружил, что при использовании вашей точной реализации и вызове parse_inputs "$@" оператор shift не имеет никакого эффекта, и независимо от того, используете вы его или нет, он не отбрасывает ваши входные данные. (Следует ли добавить это в свой пост в качестве редактирования?)
Я использую ваш первый метод, и он не работает, если в значении есть пробелы
@TyrelKostyk ваша проблема вызвана тем, что функция получает собственный массив аргументов. Поэтому, когда вы используете shift внутри функции, он удаляет элементы из массива аргументов функция, а не из всего скрипта. Решением может быть создание массива positional в вашей функции, а затем использование set -- ${positional[@]} сразу после вызова функции синтаксического анализа.
Я проголосовал против, потому что считаю, что приведенный ниже ответ является правильным.
что делает эта линия cat >/tmp/demo-getopts.sh <<'EOF'
Что означает это выражение в bash, разделенном пробелами: POSITIONAL = () ??
Используйте модуль "arguments" из bash-модули
Пример:
#!/bin/bash
. import.sh log arguments
NAME = "world"
parse_arguments "-n|--name)NAME;S" -- "$@" || {
error "Cannot parse command line."
exit 1
}
info "Hello, $NAME!"
Вот как я поступаю в функции, чтобы избежать прерывания одновременного запуска getopts где-то выше в стеке:
function waitForWeb () {
local OPTIND=1 OPTARG OPTION
local host=localhost port=8080 proto=http
while getopts "h:p:r:" OPTION; do
case "$OPTION" in
h)
host = "$OPTARG"
;;
p)
port = "$OPTARG"
;;
r)
proto = "$OPTARG"
;;
esac
done
...
}
Если вы создаете сценарии, которые взаимозаменяемы с другими утилитами, может быть полезна гибкость, указанная ниже.
Либо:
command -x=myfilename.ext --another_switch
Или же:
command -x myfilename.ext --another_switch
Вот код:
STD_IN=0
prefix = ""
key = ""
value = ""
for keyValue in "$@"
do
case "${prefix}${keyValue}" in
-i=*|--input_filename=*) key = "-i"; value = "${keyValue#*=}";;
-ss=*|--seek_from=*) key = "-ss"; value = "${keyValue#*=}";;
-t=*|--play_seconds=*) key = "-t"; value = "${keyValue#*=}";;
-|--stdin) key = "-"; value=1;;
*) value=$keyValue;;
esac
case $key in
-i) MOVIE=$(resolveMovie "${value}"); prefix = ""; key = "";;
-ss) SEEK_FROM = "${value}"; prefix = ""; key = "";;
-t) PLAY_SECONDS = "${value}"; prefix = ""; key = "";;
-) STD_IN=${value}; prefix = ""; key = "";;
*) prefix = "${keyValue} = ";;
esac
done
Это также может быть полезно знать, вы можете установить значение и, если кто-то предоставит ввод, заменить значение по умолчанию этим значением.
myscript.sh -f ./serverlist.txt или просто ./myscript.sh (и принимает значения по умолчанию)
#!/bin/bash
# --- set the value, if there is inputs, override the defaults.
HOME_FOLDER = "${HOME}/owned_id_checker"
SERVER_FILE_LIST = "${HOME_FOLDER}/server_list.txt"
while [[ $# > 1 ]]
do
key = ""
shift
case $key in
-i|--inputlist)
SERVER_FILE_LIST = ""
shift
;;
esac
done
echo "SERVER LIST = ${SERVER_FILE_LIST}"
Я использовал предыдущие ответы в качестве отправной точки, чтобы привести в порядок мой старый разбор параметров adhoc. Затем я переработал следующий код шаблона. Он обрабатывает как длинные, так и короткие параметры, используя = или аргументы, разделенные пробелами, а также несколько коротких параметров, сгруппированных вместе. Наконец, он повторно вставляет все непараметрические аргументы обратно в переменные $ 1, $ 2 ...
#!/usr/bin/env bash
# NOTICE: Uncomment if your script depends on bashisms.
#if [ -z "$BASH_VERSION" ]; then bash $0 $@ ; exit $? ; fi
echo "Before"
for i ; do echo - $i ; done
# Code template for parsing command line parameters using only portable shell
# code, while handling both long and short params, handling '-f file' and
# '-f=file' style param data and also capturing non-parameters to be inserted
# back into the shell positional parameters.
while [ -n "$1" ]; do
# Copy so we can modify it (can't modify $1)
OPT = "$1"
# Detect argument termination
if [ x"$OPT" = x"--" ]; then
shift
for OPT ; do
REMAINS = "$REMAINS \"$OPT\""
done
break
fi
# Parse current opt
while [ x"$OPT" != x"-" ] ; do
case "$OPT" in
# Handle --flag=value opts like this
-c=* | --config=* )
CONFIGFILE = "${OPT#*=}"
shift
;;
# and --flag value opts like this
-c* | --config )
CONFIGFILE = "$2"
shift
;;
-f* | --force )
FORCE=true
;;
-r* | --retry )
RETRY=true
;;
# Anything unknown is recorded for later
* )
REMAINS = "$REMAINS \"$OPT\""
break
;;
esac
# Check for multiple short options
# NOTICE: be sure to update this pattern to match valid options
NEXTOPT = "${OPT#-[cfr]}" # try removing single short opt
if [ x"$OPT" != x"$NEXTOPT" ] ; then
OPT = "-$NEXTOPT" # multiple short opts, keep going
else
break # long form, exit inner loop
fi
done
# Done with that param. move to next
shift
done
# Set the non-parameters back into the positional parameters ($1 $2 ..)
eval set -- $REMAINS
echo -e "After: \n configfile='$CONFIGFILE' \n force='$FORCE' \n retry='$RETRY' \n remains='$REMAINS'"
for i ; do echo - $i ; done
Этот код не может обрабатывать параметры с такими аргументами: -c1. И использование = для отделения коротких опций от их аргументов необычно ...
Я столкнулся с двумя проблемами с этим полезным фрагментом кода: 1) «shift» в случае «-c = foo» заканчивается тем, что съедает следующий параметр; и 2) 'c' не следует включать в шаблон «[cfr]» для комбинируемых коротких опций.
getopts отлично работает, если # 1 он у вас установлен, а # 2 вы собираетесь запускать на той же платформе. OSX и Linux (например) ведут себя по-разному в этом отношении.
Вот решение (не getopts), которое поддерживает равные, не равные и логические флаги. Например, вы можете запустить свой скрипт следующим образом:
./script --arg1=value1 --arg2 value2 --shouldClean
# parse the arguments.
COUNTER=0
ARGS=("$@")
while [ $COUNTER -lt $# ]
do
arg=${ARGS[$COUNTER]}
let COUNTER=COUNTER+1
nextArg=${ARGS[$COUNTER]}
if [[ $skipNext -eq 1 ]]; then
echo "Skipping"
skipNext=0
continue
fi
argKey = ""
argVal = ""
if [[ "$arg" =~ ^\- ]]; then
# if the format is: -key=value
if [[ "$arg" =~ \= ]]; then
argVal=$(echo "$arg" | cut -d'=' -f2)
argKey=$(echo "$arg" | cut -d'=' -f1)
skipNext=0
# if the format is: -key value
elif [[ ! "$nextArg" =~ ^\- ]]; then
argKey = "$arg"
argVal = "$nextArg"
skipNext=1
# if the format is: -key (a boolean flag)
elif [[ "$nextArg" =~ ^\- ]] || [[ -z "$nextArg" ]]; then
argKey = "$arg"
argVal = ""
skipNext=0
fi
# if the format has not flag, just a value.
else
argKey = ""
argVal = "$arg"
skipNext=0
fi
case "$argKey" in
--source-scmurl)
SOURCE_URL = "$argVal"
;;
--dest-scmurl)
DEST_URL = "$argVal"
;;
--version-num)
VERSION_NUM = "$argVal"
;;
-c|--clean)
CLEAN_BEFORE_START = "1"
;;
-h|--help|-help|--h)
showUsage
exit
;;
esac
done
Нет ответа, демонстрирует улучшенный getopt. И получивший наибольшее количество голосов ответ вводит в заблуждение: Он либо игнорирует короткие опции стиля -vfd (запрошенные OP), либо опции после позиционных аргументов (также запрошенные OP); и игнорирует ошибки синтаксического анализа. Вместо:
getopt от util-linux или ранее GNU glibc.1getopt_long() функцией C GNU glibc.getopt не может этого сделать)script.sh -o outFile file1 file2 -v (getopts этого не делает)=: script.sh --outfile=fileOut --infile fileIn (разрешая оба варианта длинны, если выполняется самостоятельный анализ)-vfd (реальная работа при самостоятельном разборе)-oOutfile или -vfdoOutfilegetopt --test → return value 4.getopt или встроенные в оболочку getopts имеют ограниченное использование.Следующие звонки
myscript -vfd ./foo/bar/someFile -o /fizz/someOtherFile
myscript -v -f -d -o/fizz/someOtherFile -- ./foo/bar/someFile
myscript --verbose --force --debug ./foo/bar/someFile -o/fizz/someOtherFile
myscript --output=/fizz/someOtherFile ./foo/bar/someFile -vfd
myscript ./foo/bar/someFile -df -v --output /fizz/someOtherFile
все возвращаются
verbose: y, force: y, debug: y, in: ./foo/bar/someFile, out: /fizz/someOtherFile
со следующим myscript
#!/bin/bash
# More safety, by turning some bugs into errors.
# Without `errexit` you don’t need ! and can replace
# PIPESTATUS with a simple $?, but I don’t do that.
set -o errexit -o pipefail -o noclobber -o nounset
# -allow a command to fail with !’s side effect on errexit
# -use return value from ${PIPESTATUS[0]}, because ! hosed $?
! getopt --test > /dev/null
if [[ ${PIPESTATUS[0]} -ne 4 ]]; then
echo 'I’m sorry, `getopt --test` failed in this environment.'
exit 1
fi
OPTIONS=dfo:v
LONGOPTS=debug,force,output:,verbose
# -regarding ! and PIPESTATUS see above
# -temporarily store output to be able to check for errors
# -activate quoting/enhanced mode (e.g. by writing out “--options”)
# -pass arguments only via -- "$@" to separate them correctly
! PARSED=$(getopt --options=$OPTIONS --longoptions=$LONGOPTS --name "$0" -- "$@")
if [[ ${PIPESTATUS[0]} -ne 0 ]]; then
# e.g. return value is 1
# then getopt has complained about wrong arguments to stdout
exit 2
fi
# read getopt’s output this way to handle the quoting right:
eval set -- "$PARSED"
d=n f=n v=n outFile=-
# now enjoy the options in order and nicely split until we see --
while true; do
case "$1" in
-d|--debug)
d=y
shift
;;
-f|--force)
f=y
shift
;;
-v|--verbose)
v=y
shift
;;
-o|--output)
outFile = "$2"
shift 2
;;
--)
shift
break
;;
*)
echo "Programming error"
exit 3
;;
esac
done
# handle non-option arguments
if [[ $# -ne 1 ]]; then
echo "$0: A single input file is required."
exit 4
fi
echo "verbose: $v, force: $f, debug: $d, in: $1, out: $outFile"
Усовершенствованный getopt 1 доступен в большинстве «bash-систем», включая Cygwin; в OS X попробуйте варить установить gnu-getopt или sudo port install getopt
2 соглашения POSIX exec() не имеют надежного способа передать двоичный NULL в аргументах командной строки; эти байты преждевременно завершают аргумент
Первая версия 3, выпущенная в 1997 году или раньше (я отслеживал ее только до 1997 года)
Спасибо за это. Только что подтверждено из таблицы функций на en.wikipedia.org/wiki/Getopts, если вам нужна поддержка длинных опций, и вы не используете Solaris, getopt - это то, что вам нужно.
Я считаю, что единственное предостережение относительно getopt заключается в том, что его нельзя использовать удобно в сценариях оболочки, где у вас может быть несколько параметров, специфичных для сценария оболочки, а затем передать параметры сценария, не являющиеся оболочкой, в оболочку исполняемого файла без изменений. Допустим, у меня есть оболочка grep под названием mygrep, и у меня есть опция --foo, специфичная для mygrep, тогда я не могу использовать mygrep --foo -A 2, и -A 2 автоматически передается на grep; Мне необходимость делать mygrep --foo -- -A 2. Вот моя реализация поверх вашего решения.
Мне нравится этот ответ. Я считаю, что утверждение с сайта wooledge.org о том, что использование расширенного getopt означает, что вы будете выполнять вдвое больше работы, вводит в заблуждение. Большинство сценариев я пишу для себя. Они должны работать в моей системе. Я никогда никому их не передаю (кроме, может быть, других на работе, если это такой сценарий). В лучшем случае мне нужно протестировать, чтобы убедиться, что установлен расширенный getopt, а затем выйти с сообщением «Для запуска этого сценария требуется расширенный getopt». Очень просто. Тем не менее, я не уверен, что когда-либо видел, чтобы расширенный getopt устанавливался по умолчанию. Это часть linux-util, который входит в состав пакета разработки для большинства дистрибутивов.
@bobpaul Ваше утверждение об util-linux неверно и также вводит в заблуждение: пакет отмечен как «необходимый» в Ubuntu / Debian. Таким образом, он всегда установлен. - О каких дистрибутивах вы говорите (где вы говорите, что их нужно установить специально)?
Я не знаю сроков разных ответов, но главный ответ делает охватывает короткие варианты стиля -vfd посредством встроенного getopts.
Обратите внимание, что это не работает на Mac по крайней мере до текущей версии 10.14.3. Доставленный товар - это BSD выпуска 1999 года ...
@jjj сноска 1 касается OS X. - Для готового решения OS X проверьте другие вопросы и ответы. Если честно: для реального программирования не используйте bash. ;-)
@BenjaminW. Ответ, получивший наибольшее количество голосов, охватывает два решения для самостоятельного анализа и getopts. Первые не объединяют короткие варианты, а вторые не разбирают варианты после аргументов, не являющихся опциями.
Есть ли ссылка на документ по команде getopt? Все, что я вижу после поиска в Google getopt, касается функции c, а не команды терминала.
@JaySomedon man getopt работает в командной строке и неплохо работает с Google. Перейдите к разделу 1, то есть к getopt (1), потому что getopt (3) - это функция C, о которой вы говорите.
Что означает главный восклицательный знак? Может ли кто-нибудь добавить ссылку?
@transang Логическое отрицание возвращаемого значения. И его побочный эффект: разрешить выполнение команды (иначе errexit прервет выполнение программы в случае ошибки). - Комментарии в сценарии расскажут больше. В противном случае: man bash
Я думаю, вам следует изменить свой ответ, чтобы не использовать set и работать с настройками по умолчанию. Это смешивание слишком большого количества вещей в одной посуде. ИМХО.
@einpoklum Есть два set, и я думаю, вы имеете в виду первое: я не согласен. Я понимаю вашу точку зрения, но я не хочу давать ответ, который не работает в среде, которую я считаю «наилучшей практикой».
Свободное смешивание флагов между позиционными аргументами:
./script.sh dumbo 127.0.0.1 --environment=production -q -d
./script.sh dumbo --environment=production 127.0.0.1 --quiet -d
можно сделать с помощью довольно краткого подхода:
# process flags
pointer=1
while [[ $pointer -le $# ]]; do
param=${!pointer}
if [[ $param != "-"* ]]; then ((pointer++)) # not a parameter flag so advance pointer
else
case $param in
# paramter-flags with arguments
-e=*|--environment=*) environment = "${param#*=}";;
--another=*) another = "${param#*=}";;
# binary flags
-q|--quiet) quiet=true;;
-d) debug=true;;
esac
# splice out pointer frame from positional list
[[ $pointer -gt 1 ]] \
&& set -- ${@:1:((pointer - 1))} ${@:((pointer + 1)):$#} \
|| set -- ${@:((pointer + 1)):$#};
fi
done
# positional remain
node_name=
ip_address=
Обычно проще не смешивать стили --flag=value и --flag value.
./script.sh dumbo 127.0.0.1 --environment production -q -d
Это немного рискованно для чтения, но все еще актуально
./script.sh dumbo --environment production 127.0.0.1 --quiet -d
Источник
# process flags
pointer=1
while [[ $pointer -le $# ]]; do
if [[ ${!pointer} != "-"* ]]; then ((pointer++)) # not a parameter flag so advance pointer
else
param=${!pointer}
((pointer_plus = pointer + 1))
slice_len=1
case $param in
# paramter-flags with arguments
-e|--environment) environment=${!pointer_plus}; ((slice_len++));;
--another) another=${!pointer_plus}; ((slice_len++));;
# binary flags
-q|--quiet) quiet=true;;
-d) debug=true;;
esac
# splice out pointer frame from positional list
[[ $pointer -gt 1 ]] \
&& set -- ${@:1:((pointer - 1))} ${@:((pointer + $slice_len)):$#} \
|| set -- ${@:((pointer + $slice_len)):$#};
fi
done
# positional remain
node_name=
ip_address=
Предлагаю свой вариант парсинга опций, который позволяет:
-s p1
--stage p1
-w somefolder
--workfolder somefolder
-sw p1 somefolder
-e=hello
Также позволяет это (может быть нежелательным):
-s--workfolder p1 somefolder
-se=hello p1
-swe=hello p1 somefolder
Перед использованием вы должны решить, следует ли использовать = для опции или нет. Это сделано для того, чтобы код оставался чистым (иш).
while [[ $# > 0 ]]
do
key = ""
while [[ ${key+x} ]]
do
case $key in
-s*|--stage)
STAGE = ""
shift # option has parameter
;;
-w*|--workfolder)
workfolder = ""
shift # option has parameter
;;
-e=*)
EXAMPLE = "${key#*=}"
break # option has been fully handled
;;
*)
# unknown option
echo Unknown option: $key #1>&2
exit 10 # either this: my preferred way to handle unknown options
break # or this: do this to signal the option has been handled (if exit isn't used)
;;
esac
# prepare for next option in this key, if any
[[ "$key" = -? || "$key" == --* ]] && unset key || key = "${key/#-?/-}"
done
shift # option(s) fully processed, proceed to next input argument
done
что означает "+ x" на $ {key + x}?
Это тест, чтобы увидеть, присутствует ли «ключ» или нет. Далее я сбрасываю ключ, и это прерывает внутренний цикл while.
while [ "$#" -gt 0 ]; do
case "" in
-n) name = ""; shift 2;;
-p) pidfile = ""; shift 2;;
-l) logfile = ""; shift 2;;
--name=*) name = "${1#*=}"; shift 1;;
--pidfile=*) pidfile = "${1#*=}"; shift 1;;
--logfile=*) logfile = "${1#*=}"; shift 1;;
--name|--pidfile|--logfile) echo " requires an argument" >&2; exit 1;;
-*) echo "unknown option: " >&2; exit 1;;
*) handle_argument ""; shift 1;;
esac
done
Это решение:
-n arg и --name=argИзвините за задержку. В моем сценарии функция handle_argument получает все аргументы, не являющиеся параметрами. Вы можете заменить эту строку чем угодно, например *) die "unrecognized argument: ", или собрать аргументы в переменную *) args+ = ""; shift 1;;.
Удивительный! Я проверил пару ответов, но это единственный, который работал во всех случаях, включая множество позиционных параметров (как до, так и после флагов).
хороший сжатый код, но использование -n и никаких других аргументов вызывает бесконечный цикл из-за ошибки на shift 2, выдавая shift дважды вместо shift 2. Предложил правку.
Я внес правку (она ожидает рассмотрения), чтобы добавить несколько полезных функций, сохранив при этом простой и небольшой код. Для более интересных функций, таких как несколько вариантов с одной буквой в одном аргументе, вам следует попробовать getopt или getopts.
Вот мое улучшенное решение ответа Бруно Броноски с использованием переменных массивов.
он позволяет вам смешивать позиции параметров и давать вам массив параметров, сохраняя порядок без опций
#!/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
Вы используете сдвиг для известных аргументов, а не для неизвестных, поэтому ваш оставшийся $@ будет состоять только из первых двух аргументов (в том порядке, в котором они передаются), что может привести к некоторым ошибкам, если вы попытаетесь использовать $@ позже. Вам не нужен сдвиг для параметров =, поскольку вы не обрабатываете пробелы и получаете значение с помощью удаления подстроки #*=
Вы правы, поскольку я создаю переменную PARAMS, мне вообще не нужно использовать shift
Подобно решение Бруно Броноски опубликовал, здесь не используется 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 Xcba Z Y X-cb-aaa-0-args Z Y X-c-bbb-1-args Z Y X -a--ccc-2-args Z Y -ba Xc Z Y b X a-c Z Y -b X -a--ccc-2-args Z Y --bbb-1-args X --aaa-0-argsВсе это приводит к:
Option CCC with arguments 'Z' and 'Y' activated!
Option BBB with argument 'X' activated!
Option AAA activated!
Doing something...
Опции с необязательными аргументами должны быть возможны после небольшой работы, например ожидая, есть ли блок без дефиса; тогда пользователю нужно будет поставить дефис перед каждым блоком, следующим за блоком с параметром, имеющим необязательный параметр. Возможно, это слишком сложно для передачи пользователю, поэтому в этом случае лучше просто вообще поставить дефис в начале.
С множеством возможных параметров все становится еще сложнее. Я бы посоветовал не создавать параметры, пытаясь быть умными, определяя, может ли аргумент быть для него или нет (например, если параметр просто принимает число в качестве необязательного аргумента), потому что это может сломаться в будущем.
Я лично предпочитаю дополнительные параметры вместо необязательных аргументов.
Как и в случае с необязательными аргументами, я не являюсь поклонником этого (кстати, есть ли ветка для обсуждения плюсов и минусов разных стилей параметров?), Но если вы хотите этого, вы, вероятно, можете реализовать это самостоятельно, как это было сделано в http://mywiki.wooledge.org/BashFAQ/035#Manual_loop с --long-with-arg=?* case и затем убирая знак равенства (это, кстати, сайт, который говорит, что объединение параметров возможно с некоторыми усилиями, но «оставил [это] в качестве упражнения для читателя», что заставило меня поверить им на слово, но я начал с царапать).
Совместимость с POSIX, работает даже на старых установках Busybox, с которыми мне приходилось иметь дело (например, отсутствовали cut, head и getopts).
Обратите внимание, что getopt(1) был недолгой ошибкой от AT&T.
getopt был создан в 1984 году, но уже похоронен в 1986 году, потому что его нельзя было использовать.
Доказательством того, что getopt очень устарел, является то, что на странице руководства getopt(1) все еще упоминается "$*" вместо "$@", который был добавлен в оболочку Bourne в 1986 году вместе со встроенной оболочкой getopts(1), чтобы иметь дело с аргументами с пробелами внутри.
Кстати: если вас интересует анализ длинных параметров в сценариях оболочки, может быть интересно узнать, что реализация getopt(3) из libc (Solaris) и ksh93 добавили унифицированную реализацию длинных параметров, которая поддерживает длинные параметры в качестве псевдонимов для коротких параметров. Это заставляет ksh93 и Bourne Shell реализовывать единый интерфейс для длинных опций через getopts.
Пример длинных опций, взятых из справочной страницы Bourne Shell:
getopts "f:(file)(input-file)o:(output-file)" OPTX "$@"
показывает, как долго можно использовать псевдонимы опций как в Bourne Shell, так и в ksh93.
См. Справочную страницу недавней Bourne Shell:
http://schillix.sourceforge.net/man/man1/bosh.1.html
и страница руководства для getopt (3) из OpenSolaris:
http://schillix.sourceforge.net/man/man3c/getopt.3c.html
и, наконец, справочная страница getopt (1) для проверки устаревшего $ *:
deploy.sh
#!/bin/bash
while [[ "$#" -gt 0 ]]; do
case in
-t|--target) target = ""; shift ;;
-u|--uglify) uglify=1 ;;
*) echo "Unknown parameter passed: "; exit 1 ;;
esac
shift
done
echo "Where to deploy: $target"
echo "Should uglify : $uglify"
Использование:
./deploy.sh -t dev -u
# OR:
./deploy.sh --target dev --uglify
Вот что я делаю. Придется while [[ "$#" > 1 ]], если я хочу поддержать завершение строки логическим флагом ./script.sh --debug dev --uglify fast --verbose. Пример: gist.github.com/hfossli/4368aa5a577742c3c9f9266ed214aa58
Ух ты! Просто и чисто! Вот как я использую это: gist.github.com/hfossli/4368aa5a577742c3c9f9266ed214aa58
Это гораздо приятнее вставлять в каждый сценарий, чем иметь дело с исходным кодом или заставлять людей задаваться вопросом, где на самом деле начинается ваша функциональность.
Предупреждение: это допускает повторяющиеся аргументы, последний аргумент преобладает. например ./script.sh -d dev -d prod приведет к deploy == 'prod'. Я все равно использовал: P :): +1:
Я использую это (спасибо!), Но обратите внимание, что он допускает пустое значение аргумента, например. ./script.sh -d не будет генерировать ошибку, а просто установит $deploy в пустую строку.
Отличный ответ, спасибо! Немного укоротил - while (( "$#" )); do вместо while [[ "$#" -gt 0 ]]; do
@CIsForCookies Спасибо! Это потому, что, afaik, синтаксис ((...)) существует только в ksh, bash и zsh, но не в обычном sh.
Я даю вам функцию parse_params, которая будет анализировать параметры из командной строки.
--all равно -all равно all=all)Приведенный ниже сценарий представляет собой демонстрацию работы с копированием и вставкой. См. Функцию show_use, чтобы понять, как использовать parse_params.
Ограничения:
-d 1)--any-param и -anyparam эквивалентныeval $(parse_params "$@") должен использоваться внутри bash функция (он не будет работать в глобальной области)#!/bin/bash
# Universal Bash parameter parsing
# Parse equal sign separated params into named local variables
# Standalone named parameter value will equal its param name (--force creates variable $force= = "force")
# Parses multi-valued named params into an array (--path=path1 --path=path2 creates ${path[*]} array)
# Puts un-named params as-is into ${ARGV[*]} array
# Additionally puts all named params as-is into ${ARGN[*]} array
# Additionally puts all standalone "option" params as-is into ${ARGO[*]} array
# @author Oleksii Chekulaiev
# @version v1.4.1 (Jul-27-2018)
parse_params ()
{
local existing_named
local ARGV=() # un-named params
local ARGN=() # named params
local ARGO=() # options (--params)
echo "local ARGV=(); local ARGN=(); local ARGO=();"
while [[ "" != "" ]]; do
# Escape asterisk to prevent bash asterisk expansion, and quotes to prevent string breakage
_escaped=${1/\*/\'\"*\"\'}
_escaped=${_escaped//\'/\\'}
_escaped=${_escaped//\"/\\"}
# If equals delimited named parameter
nonspace = "[^[:space:]]"
if [[ "" =~ ^${nonspace}${nonspace}*=..* ]]; then
# Add to named parameters array
echo "ARGN+=('$_escaped');"
# key is part before first =
local _key=$(echo "" | cut -d = -f 1)
# Just add as non-named when key is empty or contains space
if [[ "$_key" == "" || "$_key" =~ " " ]]; then
echo "ARGV+=('$_escaped');"
shift
continue
fi
# val is everything after key and = (protect from param==value error)
local _val = "${1/$_key=}"
# remove dashes from key name
_key=${_key//\-}
# skip when key is empty
# search for existing parameter name
if (echo "$existing_named" | grep "\b$_key\b" >/dev/null); then
# if name already exists then it's a multi-value named parameter
# re-declare it as an array if needed
if ! (declare -p _key 2> /dev/null | grep -q 'declare \-a'); then
echo "$_key=(\"$$_key\");"
fi
# append new value
echo "$_key+=('$_val');"
else
# single-value named parameter
echo "local $_key='$_val';"
existing_named = " $_key"
fi
# If standalone named parameter
elif [[ "" =~ ^\-${nonspace}+ ]]; then
# remove dashes
local _key=${1//\-}
# Just add as non-named when key is empty or contains space
if [[ "$_key" == "" || "$_key" =~ " " ]]; then
echo "ARGV+=('$_escaped');"
shift
continue
fi
# Add to options array
echo "ARGO+=('$_escaped');"
echo "local $_key=\"$_key\";"
# non-named parameter
else
# Escape asterisk to prevent bash asterisk expansion
_escaped=${1/\*/\'\"*\"\'}
echo "ARGV+=('$_escaped');"
fi
shift
done
}
#--------------------------- DEMO OF THE USAGE -------------------------------
show_use ()
{
eval $(parse_params "$@")
# --
echo "${ARGV[0]}" # print first unnamed param
echo "${ARGV[1]}" # print second unnamed param
echo "${ARGN[0]}" # print first named param
echo "${ARG0[0]}" # print first option param (--force)
echo "$anyparam" # print --anyparam value
echo "$k" # print k=5 value
echo "${multivalue[0]}" # print first value of multi-value
echo "${multivalue[1]}" # print second value of multi-value
[[ "$force" == "force" ]] && echo "$force is set so let the force be with you"
}
show_use "param 1" --anyparam = "my value" param2 k=5 --force --multi-value=test1 --multi-value=test2
Чтобы использовать демонстрацию для анализа параметров, которые входят в ваш сценарий bash, вы просто выполняете show_use "$@"
В основном я обнаружил, что github.com/renatosilva/easyoptions делает то же самое, но немного более массивно, чем эта функция.
Я обнаружил, что писать переносимый синтаксический анализ в сценариях настолько расстраивает, что я написал Аргбаш - генератор кода FOSS, который может генерировать код разбора аргументов для вашего сценария, плюс он имеет несколько приятных функций:
Спасибо, что написали argbash, я просто использовал его и обнаружил, что он хорошо работает. В основном я выбрал argbash, потому что это генератор кода, поддерживающий более старую версию bash 3.x, которая есть в OS X 10.11 El Capitan. Единственным недостатком является то, что подход с генератором кода означает довольно много кода в вашем основном скрипте по сравнению с вызовом модуля.
Фактически вы можете использовать Argbash таким образом, чтобы он создавал индивидуальную библиотеку синтаксического анализа только для вас, которую вы можете включить в свой скрипт, или вы можете иметь ее в отдельном файле и просто исходить из нее. Я добавил пример, чтобы продемонстрировать это, и сделал это более явным в документации.
Хорошо знать. Этот пример интересен, но все еще не совсем понятен - возможно, вы можете изменить имя сгенерированного скрипта на 'parse_lib.sh' или подобное и показать, где его вызывает основной скрипт (например, в разделе обертывания скрипта, который является более сложным вариантом использования).
Проблемы устранены в последней версии argbash: улучшена документация, представлен сценарий быстрого запуска argbash-init, и вы даже можете использовать argbash в Интернете по адресу argbash.io/generate
Главный ответ на этот вопрос показался мне немного ошибочным, когда я его попробовал - вот мое решение, которое я нашел более надежным:
boolean_arg = ""
arg_with_value = ""
while [[ $# -gt 0 ]]
do
key = "$1"
case $key in
-b|--boolean-arg)
boolean_arg=true
shift
;;
-a|--arg-with-value)
arg_with_value = "$2"
shift
shift
;;
-*)
echo "Unknown option: $1"
exit 1
;;
*)
arg_num=$(( $arg_num + 1 ))
case $arg_num in
1)
first_normal_arg = "$1"
shift
;;
2)
second_normal_arg = "$1"
shift
;;
*)
bad_args=TRUE
esac
;;
esac
done
# Handy to have this here when adding arguments to
# see if they're working. Just edit the '0' to be '1'.
if [[ 0 == 1 ]]; then
echo "first_normal_arg: $first_normal_arg"
echo "second_normal_arg: $second_normal_arg"
echo "boolean_arg: $boolean_arg"
echo "arg_with_value: $arg_with_value"
exit 0
fi
if [[ $bad_args == TRUE || $arg_num < 2 ]]; then
echo "Usage: $(basename "$0") <first-normal-arg> <second-normal-arg> [--boolean-arg] [--arg-with-value VALUE]"
exit 1
fi
Вот мое решение. Он ОЧЕНЬ гибкий и, в отличие от других, не требует внешних пакетов и аккуратно обрабатывает оставшиеся аргументы.
Использование: ./myscript -flag flagvariable -otherflag flagvar2
Все, что вам нужно сделать, это отредактировать строку validflags. Он добавляет дефис и ищет все аргументы. Затем он определяет следующий аргумент как имя флага, например.
./myscript -flag flagvariable -otherflag flagvar2
echo $flag $otherflag
flagvariable flagvar2
Основной код (короткая версия, подробная с примерами ниже, также версия с ошибками):
#!/usr/bin/env bash
#shebang.io
validflags = "rate time number"
count=1
for arg in $@
do
match=0
argval=
for flag in $validflags
do
sflag = "-"$flag
if [ "$argval" == "$sflag" ]
then
declare $flag=
match=1
fi
done
if [ "$match" == "1" ]
then
shift 2
else
leftovers=$(echo $leftovers $argval)
shift
fi
count=$(($count+1))
done
#Cleanup then restore the leftovers
shift $#
set -- $leftovers
Подробная версия со встроенными демонстрациями эха:
#!/usr/bin/env bash
#shebang.io
rate=30
time=30
number=30
echo "all args
$@"
validflags = "rate time number"
count=1
for arg in $@
do
match=0
argval=
# argval=$(echo $@ | cut -d ' ' -f$count)
for flag in $validflags
do
sflag = "-"$flag
if [ "$argval" == "$sflag" ]
then
declare $flag=
match=1
fi
done
if [ "$match" == "1" ]
then
shift 2
else
leftovers=$(echo $leftovers $argval)
shift
fi
count=$(($count+1))
done
#Cleanup then restore the leftovers
echo "pre final clear args:
$@"
shift $#
echo "post final clear args:
$@"
set -- $leftovers
echo "all post set args:
$@"
echo arg1: arg2:
echo leftovers: $leftovers
echo rate $rate time $time number $number
Последний, он выдает ошибку, если передан недопустимый аргумент.
#!/usr/bin/env bash
#shebang.io
rate=30
time=30
number=30
validflags = "rate time number"
count=1
for arg in $@
do
argval=
match=0
if [ "${argval:0:1}" == "-" ]
then
for flag in $validflags
do
sflag = "-"$flag
if [ "$argval" == "$sflag" ]
then
declare $flag=
match=1
fi
done
if [ "$match" == "0" ]
then
echo "Bad argument: $argval"
exit 1
fi
shift 2
else
leftovers=$(echo $leftovers $argval)
shift
fi
count=$(($count+1))
done
#Cleanup then restore the leftovers
shift $#
set -- $leftovers
echo rate $rate time $time number $number
echo leftovers: $leftovers
Плюсы: То, что делает, очень хорошо справляется. Он сохраняет неиспользованные аргументы, которых нет во многих других решениях. Он также позволяет вызывать переменные, не определяя их вручную в сценарии. Это также позволяет предварительное заполнение переменных, если не указан соответствующий аргумент. (См. Подробный пример).
Минусы: невозможно проанализировать одну сложную строку аргумента, например. -xcvf будет обрабатываться как один аргумент. Вы могли бы легко написать в мой дополнительный код, который добавляет эту функциональность.
В этом примере показано, как использовать getopt и eval, а также HEREDOC и shift для обработки коротких и длинных параметров с указанным ниже обязательным значением и без него. Кроме того, оператор switch / case краток и прост для понимания.
#!/usr/bin/env bash
# usage function
function usage()
{
cat << HEREDOC
Usage: $progname [--num NUM] [--time TIME_STR] [--verbose] [--dry-run]
optional arguments:
-h, --help show this help message and exit
-n, --num NUM pass in a number
-t, --time TIME_STR pass in a time string
-v, --verbose increase the verbosity of the bash script
--dry-run do a dry run, dont change any files
HEREDOC
}
# initialize variables
progname=$(basename $0)
verbose=0
dryrun=0
num_str=
time_str=
# use getopt and store the output into $OPTS
# note the use of -o for the short options, --long for the long name options
# and a : for any option that takes a parameter
OPTS=$(getopt -o "hn:t:v" --long "help,num:,time:,verbose,dry-run" -n "$progname" -- "$@")
if [ $? != 0 ] ; then echo "Error in command line arguments." >&2 ; usage; exit 1 ; fi
eval set -- "$OPTS"
while true; do
# uncomment the next line to see how shift is working
# echo "\$1:\"$1\" \$2:\"$2\""
case "$1" in
-h | --help ) usage; exit; ;;
-n | --num ) num_str = "$2"; shift 2 ;;
-t | --time ) time_str = "$2"; shift 2 ;;
--dry-run ) dryrun=1; shift ;;
-v | --verbose ) verbose=$((verbose + 1)); shift ;;
-- ) shift; break ;;
* ) break ;;
esac
done
if (( $verbose > 0 )); then
# print out all the parameters we read in
cat <<EOM
num=$num_str
time=$time_str
verbose=$verbose
dryrun=$dryrun
EOM
fi
# The rest of your script below
Наиболее важные строки приведенного выше сценария:
OPTS=$(getopt -o "hn:t:v" --long "help,num:,time:,verbose,dry-run" -n "$progname" -- "$@")
if [ $? != 0 ] ; then echo "Error in command line arguments." >&2 ; exit 1 ; fi
eval set -- "$OPTS"
while true; do
case "$1" in
-h | --help ) usage; exit; ;;
-n | --num ) num_str = "$2"; shift 2 ;;
-t | --time ) time_str = "$2"; shift 2 ;;
--dry-run ) dryrun=1; shift ;;
-v | --verbose ) verbose=$((verbose + 1)); shift ;;
-- ) shift; break ;;
* ) break ;;
esac
done
Коротко, по существу, читабельно и обрабатывает практически все (ИМХО).
Надеюсь, это кому-то поможет.
Это один из лучших ответов.
# As long as there is at least one more argument, keep looping
while [[ $# -gt 0 ]]; do
key = ""
case "$key" in
# This is a flag type option. Will catch either -f or --foo
-f|--foo)
FOO=1
;;
# Also a flag type option. Will catch either -b or --bar
-b|--bar)
BAR=1
;;
# This is an arg value type option. Will catch -o value or --output-file value
-o|--output-file)
shift # past the key and to the value
OUTPUTFILE = ""
;;
# This is an arg=value type option. Will catch -o=value or --output-file=value
-o=*|--output-file=*)
# No need to shift here since the value is part of the same string
OUTPUTFILE = "${key#*=}"
;;
*)
# Do whatever you want with extra options
echo "Unknown option '$key'"
;;
esac
# Shift after checking all the cases to get the next option
shift
done
Это позволяет вам иметь как параметры / значения, разделенные пробелом, так и одинаковые определенные значения.
Итак, вы можете запустить свой скрипт, используя:
./myscript --foo -b -o /fizz/file.txt
а также:
./myscript -f --bar -o=/fizz/file.txt
и оба должны иметь одинаковый конечный результат.
ЗА:
Допускает как -arg = значение, так и -arg значение
Работает с любым именем аргумента, которое вы можете использовать в bash
Чистый баш. Не нужно изучать / использовать getopt или getopts
МИНУСЫ:
Невозможно объединить аргументы
Я написал помощника bash, чтобы написать хороший инструмент bash
главная страница проекта: https://gitlab.mbedsys.org/mbedsys/bashopts
пример:
#!/bin/bash -ei
# load the library
. bashopts.sh
# Enable backtrace dusplay on error
trap 'bashopts_exit_handle' ERR
# Initialize the library
bashopts_setup -n "$0" -d "This is myapp tool description displayed on help message" -s "$HOME/.config/myapprc"
# Declare the options
bashopts_declare -n first_name -l first -o f -d "First name" -t string -i -s -r
bashopts_declare -n last_name -l last -o l -d "Last name" -t string -i -s -r
bashopts_declare -n display_name -l display-name -t string -d "Display name" -e "\$first_name \$last_name"
bashopts_declare -n age -l number -d "Age" -t number
bashopts_declare -n email_list -t string -m add -l email -d "Email adress"
# Parse arguments
bashopts_parse_args "$@"
# Process argument
bashopts_process_args
окажет помощь:
NAME:
./example.sh - This is myapp tool description displayed on help message
USAGE:
[options and commands] [-- [extra args]]
OPTIONS:
-h,--help Display this help
-n,--non-interactive true Non interactive mode - [$bashopts_non_interactive] (type:boolean, default:false)
-f,--first "John" First name - [$first_name] (type:string, default:"")
-l,--last "Smith" Last name - [$last_name] (type:string, default:"")
--display-name "John Smith" Display name - [$display_name] (type:string, default:"$first_name $last_name")
--number 0 Age - [$age] (type:number, default:0)
--email Email adress - [$email_list] (type:string, default:"")
наслаждаться :)
Я получаю это в Mac OS X: `` lib / bashopts.sh: line 138: declare: -A: invalid option declare: usage: declare [-afFirtx] [-p] [name [= value] ...] Ошибка в lib / bashopts.sh: 138. 'declare -x -A bashopts_optprop_name' завершился со статусом 2 Дерево вызовов: 1: lib / controller.sh: 4 source (...) Выход со статусом 1 ''
Для использования вам потребуется Bash версии 4. На Mac версия по умолчанию - 3. Вы можете использовать home brew для установки bash 4.
Вот мой подход - использование регулярного выражения.
-qwerty-q -w -e--qwerty= для предоставления атрибутов, но атрибуты совпадают до тех пор, пока не встретите дефис + пробел «разделитель», поэтому в --q=qwe tyqwe ty является одним атрибутом-o a -op attr ibute --option=att ribu te --op-tion attribute --option att-ribute действителенсценарий:
#!/usr/bin/env sh
help_menu() {
echo "Usage:
${0##*/} [-h][-l FILENAME][-d]
Options:
-h, --help
display this help and exit
-l, --logfile=FILENAME
filename
-d, --debug
enable debug
"
}
parse_options() {
case $opt in
h|help)
help_menu
exit
;;
l|logfile)
logfile=${attr}
;;
d|debug)
debug=true
;;
*)
echo "Unknown option: ${opt}\nRun ${0##*/} -h for help.">&2
exit 1
esac
}
options=$@
until [ "$options" = "" ]; do
if [[ $options =~ (^ *(--([a-zA-Z0-9-]+)|-([a-zA-Z0-9-]+))(( |=)(([\_\.\?/\a-zA-Z0-9]?[ -]?[\_\.\?a-zA-Z0-9]+)+))?(.*)|(.+)) ]]; then
if [[ ${BASH_REMATCH[3]} ]]; then # for --option[=][attribute] or --option[=][attribute]
opt=${BASH_REMATCH[3]}
attr=${BASH_REMATCH[7]}
options=${BASH_REMATCH[9]}
elif [[ ${BASH_REMATCH[4]} ]]; then # for block options -qwert[=][attribute] or single short option -a[=][attribute]
pile=${BASH_REMATCH[4]}
while (( ${#pile} > 1 )); do
opt=${pile:0:1}
attr = ""
pile=${pile/${pile:0:1}/}
parse_options
done
opt=$pile
attr=${BASH_REMATCH[7]}
options=${BASH_REMATCH[9]}
else # leftovers that don't match
opt=${BASH_REMATCH[10]}
options = ""
fi
parse_options
fi
done
Как этот. Может быть, просто добавьте параметр -e для вывода с новой строки.
Предположим, мы создали сценарий оболочки с именем test_args.sh, как показано ниже.
#!/bin/sh
until [ $# -eq 0 ]
do
name=${1:1}; shift;
if [[ -z "" || == -* ]] ; then eval "export $name=true"; else eval "export $name = "; shift; fi
done
echo "year=$year month=$month day=$day flag=$flag"
После запускаем следующую команду:
sh test_args.sh -year 2017 -flag -month 12 -day 22
Результатом будет:
year=2017 month=12 day=22 flag=true
Это использует тот же подход, что и Ответ Ноя, но имеет меньше проверок / мер безопасности. Это позволяет нам записывать произвольные аргументы в среду сценария, и я почти уверен, что ваше использование eval здесь может разрешить внедрение команд.
Я хочу отправить свой проект: https://github.com/flyingangel/argparser
source argparser.sh
parse_args "$@"
Просто как тот. Среда будет заполнена переменными с тем же именем, что и аргументы
Просто и легко изменить, параметры могут быть в любом порядке. это можно изменить, чтобы принимать параметры в любой форме (-a, --a, a и т. д.).
for arg in "$@"
do
key=$(echo $arg | cut -f1 -d=)`
value=$(echo $arg | cut -f2 -d=)`
case "$key" in
name|-name) read_name=$value;;
id|-id) read_id=$value;;
*) echo "I dont know what to do with this"
ease
done
В этом скрипте ease неверен и должен быть заменен на esac, чтобы закрыть блок case
Расширяя ответ @bruno-bronosky, я добавил «препроцессор» для обработки некоторого общего форматирования:
--longopt=val на --longopt val-xyz на -x -y -z-- для обозначения конца флагов#!/bin/bash
# Report usage
usage() {
echo "Usage:"
echo "$(basename $0) [options] [--] [file1, ...]"
# Optionally exit with a status code
if [ -n "$1" ]; then
exit "$1"
fi
}
invalid() {
echo "ERROR: Unrecognized argument: $1" >&2
usage 1
}
# Pre-process options to:
# - expand -xyz into -x -y -z
# - expand --longopt=arg into --longopt arg
ARGV=()
END_OF_OPT=
while [[ $# -gt 0 ]]; do
arg = "$1"; shift
case "${END_OF_OPT}${arg}" in
--) ARGV+=("$arg"); END_OF_OPT=1 ;;
--*=*)ARGV+=("${arg%%=*}" "${arg#*=}") ;;
--*) ARGV+=("$arg"); END_OF_OPT=1 ;;
-*) for i in $(seq 2 ${#arg}); do ARGV+=("-${arg:i-1:1}"); done ;;
*) ARGV+=("$arg") ;;
esac
done
# Apply pre-processed options
set -- "${ARGV[@]}"
# Parse options
END_OF_OPT=
POSITIONAL=()
while [[ $# -gt 0 ]]; do
case "${END_OF_OPT}${1}" in
-h|--help) usage 0 ;;
-p|--password) shift; PASSWORD = "$1" ;;
-u|--username) shift; USERNAME = "$1" ;;
-n|--name) shift; names+=("$1") ;;
-q|--quiet) QUIET=1 ;;
-C|--copy) COPY=1 ;;
-N|--notify) NOTIFY=1 ;;
--stdin) READ_STDIN=1 ;;
--) END_OF_OPT=1 ;;
-*) invalid "$1" ;;
*) POSITIONAL+=("$1") ;;
esac
shift
done
# Restore positional parameters
set -- "${POSITIONAL[@]}"
Вот getopts, который выполняет синтаксический анализ с минимальным количеством кода и позволяет вам определить, что вы хотите извлечь в одном случае, используя eval с подстрокой.
В основном eval "local key='val'"
function myrsync() {
local backup=("${@}") args=(); while [[ $# -gt 0 ]]; do k = "";
case "$k" in
---sourceuser|---sourceurl|---targetuser|---targeturl|---file|---exclude|---include)
eval "local ${k:3}=''"; shift; shift # Past two arguments
;;
*) # Unknown option
args+=(""); shift; # Past argument only
;;
esac
done; set -- "${backup[@]}" # Restore $@
echo "${sourceurl}"
}
Объявляет переменные как локальные, а не глобальные, как в большинстве ответов здесь.
Называется как:
myrsync ---sourceurl http://abc.def.g ---sourceuser myuser ...
$ {K: 3} - это в основном подстрока для удаления первого --- из ключа.
Есть несколько способов синтаксического анализа аргументов cmdline (например, GNU getopt (не переносимый) vs BSD (MacOS) getopt vs getopts) - все это проблематично. Это решение
= между опцией и аргументом-vxf--color против --color=always),--, чтобы сигнализировать об окончании опций, иПримеры: любой из
# flag
-f
--foo
# option with required argument
-b"Hello World"
-b "Hello World"
--bar "Hello World"
--bar = "Hello World"
# option with optional argument
--baz
--baz = "Optional Hello"
#!/usr/bin/env bash
usage() {
cat - >&2 <<EOF
NAME
program-name.sh - Brief description
SYNOPSIS
program-name.sh [-h|--help]
program-name.sh [-f|--foo]
[-b|--bar <arg>]
[--baz[=<arg>]]
[--]
FILE ...
REQUIRED ARGUMENTS
FILE ...
input files
OPTIONS
-h, --help
Prints this and exits
-f, --foo
A flag option
-b, --bar <arg>
Option requiring an argument <arg>
--baz[=<arg>]
Option that has an optional argument <arg>. If <arg>
is not specified, defaults to 'DEFAULT'
--
Specify end of options; useful if the first non option
argument starts with a hyphen
EOF
}
fatal() {
for i; do
echo -e "${i}" >&2
done
exit 1
}
# For long option processing
next_arg() {
if [[ $OPTARG == *=* ]]; then
# for cases like '--opt=arg'
OPTARG = "${OPTARG#*=}"
else
# for cases like '--opt arg'
OPTARG = "${args[$OPTIND]}"
OPTIND=$((OPTIND + 1))
fi
}
# ':' means preceding option character expects one argument, except
# first ':' which make getopts run in silent mode. We handle errors with
# wildcard case catch. Long options are considered as the '-' character
optspec = ":hfb:-:"
args=("" "$@") # dummy first element so $1 and $args[1] are aligned
while getopts "$optspec" optchar; do
case "$optchar" in
h) usage; exit 0 ;;
f) foo=1 ;;
b) bar = "$OPTARG" ;;
-) # long option processing
case "$OPTARG" in
help)
usage; exit 0 ;;
foo)
foo=1 ;;
bar|bar=*) next_arg
bar = "$OPTARG" ;;
baz)
baz=DEFAULT ;;
baz=*) next_arg
baz = "$OPTARG" ;;
-) break ;;
*) fatal "Unknown option '--${OPTARG}'" "see '${0} --help' for usage" ;;
esac
;;
*) fatal "Unknown option: '-${OPTARG}'" "See '${0} --help' for usage" ;;
esac
done
shift $((OPTIND-1))
if [ "$#" -eq 0 ]; then
fatal "Expected at least one required argument FILE" \
"See '${0} --help' for usage"
fi
echo "foo=$foo, bar=$bar, baz=$baz, files=${@}"
Хотел поделиться, что сделал для разбора вариантов. Некоторые из моих потребностей не были удовлетворены приведенными здесь ответами, поэтому мне пришлось придумать следующее: https://github.com/MihirLuthra/bash_option_parser
Это поддерживает:
Допустим, у нас есть команда fruit, которая используется следующим образом:
fruit <fruit-name> ...
[-e|—-eat|—-chew]
[-c|--cut <how> <why>]
<command> [<args>]
-e не принимает аргументов
-c принимает два аргумента, то есть как резать и зачем резать
Сам fruit принимает по крайней мере один аргумент.
<command> предназначен для подопций, таких как apple, orange и т. д. (Аналогично git, у которого есть подопции commit, push и т. д.)
Итак, чтобы разобрать его:
parse_options \
'fruit' '1 ...' \
'-e' , '--eat' , '--chew' '0' \
'-c' , '--cut' '1 1' \
'apple' 'S' \
'orange' 'S' \
';' \
"$@"
Теперь, если возникла какая-либо ошибка использования, ее можно распечатать с помощью option_parser_error_msg следующим образом:
retval=$?
if [ $retval -ne 0 ]; then
# this will manage error messages if
# insufficient or extra args are supplied
option_parser_error_msg "$retval"
# This will print the usage
print_usage 'fruit'
exit 1
fi
Чтобы проверить, были ли переданы какие-то параметры,
if [ -n "${OPTIONS[-c]}" ]
then
echo "-c was passed"
# args can be accessed in a 2D-array-like format
echo "Arg1 to -c = ${ARGS[-c,0]}"
echo "Arg2 to -c = ${ARGS[-c,1]}"
fi
Синтаксический анализ подопции также может быть выполнен путем передачи $shift_count в parse_options_detailed, что заставляет его начать синтаксический анализ после сдвига аргументов для достижения аргументов подопции. Это продемонстрировано в этом пример.
Подробное описание приведено в ридми и примерах. в хранилище.
Меня вдохновил относительно простой ответ @bronson, и я хотел попытаться улучшить его (не добавляя излишней сложности). Вот результат:
getopt*-n [arg], -abn [arg], --name [arg]и--name=arg;$@ после цикла;--заставить оставшиеся аргументы должны рассматриваться как позиционные;getopt(s) или внешних инструментов (одна функция использует простую команду sed);# Convenience functions.
usage_error () { echo >&2 "$(basename $0): $1"; exit 2; }
assert_argument () { test "$1" != "$EOL" || usage_error "$2 requires an argument"; }
# One loop, nothing more.
EOL=$(echo '\01\03\03\07')
if [ "$#" != 0 ]; then
set -- "$@" "$EOL"
while [ "$1" != "$EOL" ]; do
opt = "$1"; shift
case "$opt" in
# Your options go here.
-f|--flag) flag=true;;
-n|--name) assert_argument "$1" $opt; name = "$1"; shift;;
-|''|[^-]*) set -- "$@" "$opt";; # positional argument, rotate to the end
# Extra features (you may remove any line you don't need):
--*=*) set -- "${opt%%=*}" "${opt#*=}" "$@";; # convert '--name=arg' to '--name' 'arg'
-[^-]?*) set -- $(echo "${opt#-}" | sed 's/\(.\)/ -\1/g') "$@";; # convert '-abc' to '-a' '-b' '-c'
--) while [ "$1" != "$EOL" ]; do set -- "$@" "$1"; shift; done;; # process remaining arguments as positional
-*) usage_error "unknown option: '$opt'";; # catch misspelled options
*) usage_error "this should NEVER happen ($opt)";; # sanity test for previous patterns
esac
done
shift # $EOL
fi
# Do something cool with "$@"... \o/
Примечание: Я знаю ... Аргумент с двоичный образец0x01030307 может нарушить логику. Но если кто-то передает такой аргумент в командной строке, он этого заслуживает.
Хороший умный способ сделать это. Я использую его с этого момента, пока не будет обнаружен лучший способ или, возможно, ошибка ;-)
Я написал сценарий, который может легко помочь с анализом аргументов командной строки - https://github.com/unfor19/bargs
$ bash example.sh -n Willy --gender male -a 99
Name: Willy
Age: 99
Gender: male
Location: chocolate-factory
$ bash example.sh -n Meir --gender male
[ERROR] Required argument: age
Usage: bash example.sh -n Willy --gender male -a 99
--person_name | -n [Willy] What is your name?
--age | -a [Required]
--gender | -g [Required]
--location | -l [chocolate-factory] insert your location
$ bash example.sh -h
Usage: bash example.sh -n Willy --gender male -a 99
--person_name | -n [Willy] What is your name?
--age | -a [Required]
--gender | -g [Required]
--location | -l [chocolate-factory] insert your location
https://github.com/ko1nksm/getoptions
варианты - это новый синтаксический анализатор опций (генератор), написанный на POSIX-совместимом сценарии оболочки и выпущенный в августе 2020 года. Он предназначен для тех, кто хочет поддерживать стандартный синтаксис опций в сценариях оболочки без башизмов. Поддерживаемые синтаксисы: -a, +a, -abc, -vvv, -p VALUE, -pVALUE, --flag, --no-flag, --param VALUE, --param=VALUE, --option[=VALUE], --no-option--.
Он поддерживает подкоманды, проверку, сокращенные параметры и автоматическое создание справки. и работает с большинством ОС (Linux, macOS, BSD, Windows и т. д.) и всеми оболочками POSIX (dash, bash, ksh, zsh и т. д.).
#!/bin/sh
. ./lib/getoptions.sh
. ./lib/getoptions_help.sh
parser_definition() {
setup REST help:usage -- "Usage: ${2##*/} [options] [arguments]" ''
flag FLAG -f --flag -- "--flag option"
param PARAM -p --param -- "--param option"
option OPTION -o --option on:"default" -- "--option option"
disp :usage -h --help
disp VERSION --version
}
eval "$(getoptions parser_definition parse "$0")"
parse "$@"
eval "set -- $REST"
echo "FLAG: $FLAG"
echo "PARAM: $PARAM"
echo "OPTION: $OPTION"
printf ': %s\n' "$@" # Rest arguments
Это также генератор синтаксического анализа параметров, генерирующий следующий код синтаксического анализа параметров.
FLAG=''
PARAM=''
OPTION=''
REST=''
parse() {
OPTIND=$(($#+1))
while OPTARG= && [ $# -gt 0 ]; do
case $1 in
--?*=*) OPTARG=$1; shift
eval 'set -- "${OPTARG%%\=*}" "${OPTARG#*\=}"' ${1+'"$@"'}
;;
--no-*) unset OPTARG ;;
-[po]?*) OPTARG=$1; shift
eval 'set -- "${OPTARG%"${OPTARG#??}"}" "${OPTARG#??}"' ${1+'"$@"'}
;;
-[!-]?*) OPTARG=$1; shift
eval 'set -- "${OPTARG%"${OPTARG#??}"}" "-${OPTARG#??}"' ${1+'"$@"'}
OPTARG= ;;
esac
case $1 in
-f | --flag)
[ "${OPTARG:-}" ] && OPTARG=${OPTARG#*\=} && set -- noarg "$1" && break
eval '[ ${OPTARG+x} ] &&:' && OPTARG='1' || OPTARG=''
FLAG = "$OPTARG"
;;
-p | --param)
[ $# -le 1 ] && set -- required "$1" && break
OPTARG=$2
PARAM = "$OPTARG"
shift ;;
-o | --option)
set -- "$1" "$@"
[ ${OPTARG+x} ] && {
case $1 in --no-*) set -- noarg "${1%%\=*}"; break; esac
[ "${OPTARG:-}" ] && { shift; OPTARG=$2; } || OPTARG='default'
} || OPTARG=''
OPTION = "$OPTARG"
shift ;;
-h | --help)
usage
exit 0 ;;
--version)
echo "${VERSION}"
exit 0 ;;
--) shift
while [ $# -gt 0 ]; do
REST = "${REST} \"\${$((${OPTIND:-0}-$#))}\""
shift
done
break ;;
[-]?*) set -- unknown "$1" && break ;;
*) REST = "${REST} \"\${$((${OPTIND:-0}-$#))}\""
esac
shift
done
[ $# -eq 0 ] && { OPTIND=1; unset OPTARG; return 0; }
case $1 in
unknown) set -- "Unrecognized option: $2" "$@" ;;
noarg) set -- "Does not allow an argument: $2" "$@" ;;
required) set -- "Requires an argument: $2" "$@" ;;
pattern:*) set -- "Does not match the pattern (${1#*:}): $2" "$@" ;;
*) set -- "Validation error ($1): $2" "$@"
esac
echo "$1" >&2
exit 1
}
usage() {
cat<<'GETOPTIONSHERE'
Usage: example.sh [options] [arguments]
-f, --flag --flag option
-p, --param PARAM --param option
-o, --option[=OPTION] --option option
-h, --help
--version
GETOPTIONSHERE
}
Я использую его для перебора ключ => значение с конца. После цикла перехватывается первый необязательный аргумент.
Используется ./script.sh необязательный-первый-аргумент -key value -key2 value2
#!/bin/sh
a=$(($#-1))
b=$(($#))
while [ $a -gt 0 ]; do
eval 'key = "$'$a'"; value = "$'$b'"'
echo "$key => $value"
b=$(($b-2))
a=$(($a-2))
done
unset a b key value
[ $(($#%2)) -ne 0 ] && echo "first_arg = "
Конечно, вы можете сделать это слева направо с небольшими изменениями.
Этот фрагмент кода показывает пары ключ => значение и первый аргумент, если он существует.
#!/bin/sh
a=$((1+$#%2))
b=$((1+$a))
[ $(($#%2)) -ne 0 ] && echo "first_arg = "
while [ $a -lt $# ]; do
eval 'key = "$'$a'"; value = "$'$b'"'
echo "$key => $value"
b=$(($b+2))
a=$(($a+2))
done
unset a b key value
Протестировано 100 000 аргументов, быстро.
Вы также можете перебирать ключ => значение и первый необязательный аргумент слева направо без eval:
#!/bin/sh
a=$(($#%2))
b=0
[ $a -eq 1 ] && echo "first_arg = "
for value; do
if [ $b -gt $a -a $(($b%2)) -ne $a ]; then
echo "$key => $value"
fi
key = "$value"
b=$((1+$b))
done
unset a b key value
Для пользователей 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