Как проанализировать аргументы командной строки в Bash?
Скажем, у меня есть script, который вызывается с помощью этой строки:
./myscript -vfd ./foo/bar/someFile -o /fizz/someOtherFile
или этот:
./myscript -v -f -d -o /fizz/someOtherFile ./foo/bar/someFile
Что принято в этом синтаксическом анализе, чтобы в каждом случае (или некоторая комбинация из двух) $v
, $f
и $d
все были установлены на true
, а $outFile
будет равно до /fizz/someOtherFile
?
Ответы
Ответ 1
Обновление: прошло более 5 лет с тех пор, как я начал этот ответ. Спасибо за МНОГО отличных правок/комментариев/предложений. Чтобы сэкономить время на техническое обслуживание, я изменил блок кода, чтобы он был полностью готов к копированию и вставке. Пожалуйста, не оставляйте комментарии типа "Что если вы изменили X на Y…". Вместо этого скопируйте и вставьте блок кода, просмотрите выходные данные, внесите изменения, повторно запустите сценарий и прокомментируйте: "Я изменил X на Y и…". У меня нет времени проверить ваши идеи и сказать, работают ли они.
Способ № 1: Использование bash без getopt [s]
Два распространенных способа передачи аргументов пары ключ-значение:
Bash Space -s отделен (например, --option argument
) (без getopt [s])
demo-space-separated.sh -e conf -s/etc -l/usr/lib/etc/hosts
использования 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 равен -s (например, --option=argument
) (без getopt [s])
demo-equals-separated.sh -e=conf -s=/etc -l=/usr/lib/etc/hosts
использования 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 "[email protected]"
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/[^=]*=//''
'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: [email protected]"
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
являются:
- Он более переносим и будет работать в других оболочках, например, в
dash
. - Он может обрабатывать несколько отдельных параметров, таких как
-vf filename
типичным способом Unix, автоматически.
Недостаток getopts
заключается в том, что он может обрабатывать только короткие опции (-h
, а не --help
) без дополнительного кода.
Существует учебник getopts, который объясняет, что означает весь синтаксис и переменные. В bash есть также help getopts
, которая может быть информативной.
Ответ 2
Нет ответа упоминает расширенный Getopt. И верхний ответ -v вводит в заблуждение: он либо игнорирует короткие опции в стиле -vfd
(запрошенные OP), либо опции после позиционных аргументов (также запрошенные OP); и он игнорирует ошибки синтаксического анализа. Вместо:
- Используйте улучшенный
getopt
из util-linux или ранее GNU glibc. 1 - Он работает с
getopt_long()
функцией C в GNU glibc. - Имеет все полезные отличительные особенности (у других их нет):
- обрабатывает пробелы, заключая в кавычки символы и даже двоичные в аргументах 2 (не расширенный
getopt
не может этого сделать) - в конце он может обрабатывать опции:
script.sh -o outFile file1 file2 -v
(getopts
не делает) - позволяет
=
-style длинные опции: script.sh --outfile=fileOut --infile fileIn
(разрешение обоих 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 'Im 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 -- "[email protected]" to separate them correctly
! PARSED=$(getopt --options=$OPTIONS --longoptions=$LONGOPTS --name "$0" -- "[email protected]")
if [[ ${PIPESTATUS[0]} -ne 0 ]]; then
# e.g. return value is 1
# then getopt has complained about wrong arguments to stdout
exit 2
fi
# read getopts output this way to handle the quoting right:
eval set -- "$PARSED"
d=n f=n v=n outFile=-
# now enjoy the options in order and nicely split until we see --
while true; do
case "$1" in
-d|--debug)
d=y
shift
;;
-f|--force)
f=y
shift
;;
-v|--verbose)
v=y
shift
;;
-o|--output)
outFile="$2"
shift 2
;;
--)
shift
break
;;
*)
echo "Programming error"
exit 3
;;
esac
done
# handle non-option arguments
if [[ $# -ne 1 ]]; then
echo "$0: A single input file is required."
exit 4
fi
echo "verbose: $v, force: $f, debug: $d, in: $1, out: $outFile"
1 расширенный getopt доступен на большинстве "bash-систем", включая Cygwin; на OS X попробуйте установить bnu gnu-getopt или sudo port install getopt
2 соглашения POSIX exec()
не имеют надежного способа передачи двоичного NULL в аргументах командной строки; эти байты преждевременно заканчивают спор
3 первая версия, выпущенная в 1997 году или ранее (я отслеживал ее только в 1997 году)
Ответ 3
от: digitalpeer.com с небольшими изменениями
Использование myscript.sh -p=my_prefix -s=dirname -l=libname
#!/bin/bash
for i in "[email protected]"
do
case $i in
-p=*|--prefix=*)
PREFIX="${i#*=}"
;;
-s=*|--searchpath=*)
SEARCHPATH="${i#*=}"
;;
-l=*|--lib=*)
DIR="${i#*=}"
;;
--default)
DEFAULT=YES
;;
*)
# unknown option
;;
esac
done
echo PREFIX = ${PREFIX}
echo SEARCH PATH = ${SEARCHPATH}
echo DIRS = ${DIR}
echo DEFAULT = ${DEFAULT}
Чтобы лучше понять ${i#*=}
найдите "Удаление подстроки" в этом руководстве. Функционально он эквивалентен 'sed 's/[^=]*=//' <<< "$i"'
который вызывает ненужный подпроцесс или 'echo "$i" | sed 's/[^=]*=//''
'echo "$i" | sed 's/[^=]*=//''
который вызывает два ненужных подпроцесса.
Ответ 4
getopt()
/getopts()
- хороший вариант. Украден из здесь:
Простое использование "getopt" показано в этом мини- script:
#!/bin/bash
echo "Before getopt"
for i
do
echo $i
done
args=`getopt abc:d $*`
set -- $args
echo "After getopt"
for i
do
echo "-->$i"
done
Мы сказали, что любой из -a, -b, -c или -d, но за -c следует аргумент (говорит "c:" ).
Если мы назовем это "g" и попробуем:
bash-2.05a$ ./g -abc foo
Before getopt
-abc
foo
After getopt
-->-a
-->-b
-->-c
-->foo
-->--
Начнем с двух аргументов и "getopt" разрывает варианты и ставит каждого в свои собственные аргументы. Это также добавлено "-".
Ответ 5
Более лаконичный способ
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
Ответ 6
Рискуя добавить другой пример для игнорирования, вот моя схема.
- обрабатывает
-n arg
и --name=arg
- разрешает аргументы в конце
- показывает правильные ошибки, если что-то написано неправильно.
- совместимый, не использует bashisms
- читаемый, не требует сохранения состояния в цикле
Надеюсь, что это полезно кому-то.
while [ "$#" -gt 0 ]; do
case "$1" in
-n) name="$2"; shift 2;;
-p) pidfile="$2"; shift 2;;
-l) logfile="$2"; shift 2;;
--name=*) name="${1#*=}"; shift 1;;
--pidfile=*) pidfile="${1#*=}"; shift 1;;
--logfile=*) logfile="${1#*=}"; shift 1;;
--name|--pidfile|--logfile) echo "$1 requires an argument" >&2; exit 1;;
-*) echo "unknown option: $1" >&2; exit 1;;
*) handle_argument "$1"; shift 1;;
esac
done
Ответ 7
Я опаздываю на этот вопрос на 4 года, но хочу вернуть. Я использовал более ранние ответы в качестве отправной точки, чтобы убрать мой старый синтаксический анализ adhoc. Затем я переработал следующий код шаблона. Он обрабатывает как длинные, так и короткие параметры, используя аргументы = или пробел, а также несколько коротких параметров, сгруппированных вместе. Наконец, он снова вставляет любые аргументы без параметров обратно в переменные $1, $2.. Надеюсь, это будет полезно.
#!/usr/bin/env bash
# NOTICE: Uncomment if your script depends on bashisms.
#if [ -z "$BASH_VERSION" ]; then bash $0 [email protected] ; 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
Ответ 8
Я нашел, что нужно написать переносимый синтаксический анализ в сценариях, так что разочарование в том, что я написал Argbash - генератор кода FOSS, который может генерировать код анализа аргументов для вашего script плюс он имеет некоторые приятные функции:
https://argbash.io
Ответ 9
Мой ответ в основном основан на ответе Бруно Броноски, но я как бы размял его две чистые реализации bash в один, который я использую довольно часто.
# As long as there is at least one more argument, keep looping
while [[ $# -gt 0 ]]; do
key="$1"
case "$key" in
# This is a flag type option. Will catch either -f or --foo
-f|--foo)
FOO=1
;;
# Also a flag type option. Will catch either -b or --bar
-b|--bar)
BAR=1
;;
# This is an arg value type option. Will catch -o value or --output-file value
-o|--output-file)
shift # past the key and to the value
OUTPUTFILE="$1"
;;
# This is an arg=value type option. Will catch -o=value or --output-file=value
-o=*|--output-file=*)
# No need to shift here since the value is part of the same string
OUTPUTFILE="${key#*=}"
;;
*)
# Do whatever you want with extra options
echo "Unknown option '$key'"
;;
esac
# Shift after checking all the cases to get the next option
shift
done
Это позволяет вам иметь как разделенные пробелом опции/значения, так и равные определенные значения.
Итак, вы можете запустить script, используя:
./myscript --foo -b -o /fizz/file.txt
а также:
./myscript -f --bar -o=/fizz/file.txt
и оба должны иметь тот же конечный результат.
ПЛЮСЫ:
-
Позволяет использовать как -arg = значение и -arg значение
-
Работает с любым именем arg, которое вы можете использовать в bash
- Значение -a или -arg или -arg или -a-r-g или что-то еще
-
Pure bash. Нет необходимости изучать/использовать getopt или getopts
МИНУСЫ:
Это единственные плюсы/минусы, которые я могу придумать с головы.
Ответ 10
Я думаю, что это достаточно просто, чтобы использовать:
#!/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
Ответ 11
Развернувшись на отличном ответе от @guneysus, вот настройка, которая позволяет пользователю использовать любой синтаксис, который они предпочитают, например
command -x=myfilename.ext --another_switch
против
command -x myfilename.ext --another_switch
То есть равные могут быть заменены пробелами.
Эта "нечеткая интерпретация" может быть не по вашему вкусу, но если вы создаете скрипты, которые взаимозаменяемы с другими утилитами (как в случае с моими, которые должны работать с ffmpeg), гибкость полезна.
STD_IN=0
prefix=""
key=""
value=""
for keyValue in "[email protected]"
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
Ответ 12
getopts отлично работает, если # 1 у вас он установлен, а # 2 вы собираетесь запускать его на той же платформе. OSX и Linux (например) ведут себя по-другому в этом отношении.
Вот решение (не getopts), которое поддерживает равные, не равные и логические флаги. Например, вы можете запустить свой скрипт таким образом:
./script --arg1=value1 --arg2 value2 --shouldClean
# parse the arguments.
COUNTER=0
ARGS=("[email protected]")
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
Ответ 13
Я даю вам функцию parse_params
которая будет анализировать параметры из командной строки.
- Это чисто решение Bash, никаких дополнительных утилит.
- Не загрязняет глобальные рамки.
- Без усилий возвращает вам простые в использовании переменные, на которых вы могли бы построить дополнительную логику.
- Количество тире перед параметрами не имеет значения (
--all
равно -all
равно all=all
)
Сценарий ниже является рабочей демонстрацией копирования-вставки. Смотрите функцию show_use
чтобы понять, как использовать parse_params
.
Ограничения:
- Не поддерживает разделенные пробелом параметры (
-d 1
) - Имена параметров потеряют тире, поэтому
--any-param
и -anyparam
эквивалентны -
eval $(parse_params "[email protected]")
должен использоваться внутри функции 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 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 "[email protected]")
# --
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
Ответ 14
Вот как я делаю в функции, чтобы избежать нарушения работы 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
...
}
Ответ 15
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
Ответ 16
Я хотел бы предложить свою версию синтаксического анализа параметров, которая позволяет следующее:
-s p1
--stage p1
-w somefolder
--workfolder somefolder
-sw p1 somefolder
-e=hello
Также позволяет это (может быть нежелательно):
-s--workfolder p1 somefolder
-se=hello p1
-swe=hello p1 somefolder
Вы должны решить перед использованием if = для использования по опции или нет. Это значит, что код чист (ish).
while [[ $# > 0 ]]
do
key="$1"
while [[ ${key+x} ]]
do
case $key in
-s*|--stage)
STAGE="$2"
shift # option has parameter
;;
-w*|--workfolder)
workfolder="$2"
shift # option has parameter
;;
-e=*)
EXAMPLE="${key#*=}"
break # option has been fully handled
;;
*)
# unknown option
echo Unknown option: $key #1>&2
exit 10 # either this: my preferred way to handle unknown options
break # or this: do this to signal the option has been handled (if exit isn't used)
;;
esac
# prepare for next option in this key, if any
[[ "$key" = -? || "$key" == --* ]] && unset key || key="${key/#-?/-}"
done
shift # option(s) fully processed, proceed to next input argument
done
Ответ 17
Обратите внимание, что getopt(1)
была короткой живой ошибкой от AT & T.
getopt был создан в 1984 году, но уже похоронен в 1986 году, потому что он не был действительно полезен.
Доказательство того, что getopt
очень устарело, состоит в том, что man-страница getopt(1)
по-прежнему упоминает "$*"
вместо "[email protected]"
, которая была добавлена в оболочку Bourne в 1986 году вместе с оболочкой getopts(1)
встроенный для обработки аргументов с пробелами внутри.
BTW: если вам интересно разобрать длинные параметры в сценариях оболочки, может быть интересно узнать, что реализация getopt(3)
из libc (Solaris) и ksh93
добавила единую длинную опционную реализацию, которая поддерживает длинные параметры как псевдонимы для коротких опций. Это приводит к тому, что ksh93
и Bourne Shell
реализуют единый интерфейс для длинных опций через getopts
.
Пример для длинных параметров, взятых из страницы руководства Bourne Shell:
getopts "f:(file)(input-file)o:(output-file)" OPTX "[email protected]"
показывает, как долго альтернативные псевдонимы могут использоваться как в 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
Ответ 18
Решение, которое сохраняет необработанные аргументы. Включены демонстрации.
Вот мое решение. Он ОЧЕНЬ гибкий и в отличие от других, не должен требовать внешних пакетов и обрабатывать оставшиеся аргументы чисто.
Использование: ./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 [email protected]
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
[email protected]"
validflags="rate time number"
count=1
for arg in [email protected]
do
match=0
argval=$1
# argval=$(echo [email protected] | 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:
[email protected]"
shift $#
echo "post final clear args:
[email protected]"
set -- $leftovers
echo "all post set args:
[email protected]"
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 [email protected]
do
argval=$1
match=0
if [ "${argval:0:1}" == "-" ]
then
for flag in $validflags
do
sflag="-"$flag
if [ "$argval" == "$sflag" ]
then
declare $flag=$2
match=1
fi
done
if [ "$match" == "0" ]
then
echo "Bad argument: $argval"
exit 1
fi
shift 2
else
leftovers=$(echo $leftovers $argval)
shift
fi
count=$(($count+1))
done
#Cleanup then restore the leftovers
shift $#
set -- $leftovers
echo rate $rate time $time number $number
echo leftovers: $leftovers
Плюсы: Что он делает, он отлично справляется. Он сохраняет неиспользуемые аргументы, которые многие другие решения здесь нет. Он также позволяет вызывать переменные без указания вручную в script. Он также позволяет предустановить переменные, если не указан соответствующий аргумент. (См. Подробный пример).
Минусы: не удается разобрать одну сложную строку arg, например. -xcvf будет обрабатываться как один аргумент. Вы могли бы легко написать дополнительный код в мой, который добавляет эту функциональность.
Ответ 19
Предположим, мы создаем оболочку script с именем test_args.sh
, как следует
#!/bin/sh
until [ $# -eq 0 ]
do
name=${1:1}; shift;
if [[ -z "$1" || $1 == -* ]] ; then eval "export $name=true"; else eval "export $name=$1"; shift; fi
done
echo "year=$year month=$month day=$day flag=$flag"
После выполнения следующей команды:
sh test_args.sh -year 2017 -flag -month 12 -day 22
Вывод будет:
year=2017 month=12 day=22 flag=true
Ответ 20
Я хочу представить свой проект: https://github.com/flyingangel/argparser
source argparser.sh
parse_args "[email protected]"
Просто как тот. Окружающая среда будет заполнена переменными с тем же именем, что и аргументы
Ответ 21
Смешивание позиционных и флаговых аргументов
- 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
Ответ 22
В этом примере показано, как использовать 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" -- "[email protected]")
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" -- "[email protected]")
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
Коротко, по сути, читабельно и обрабатывает практически все (ИМХО).
Надеюсь, что это помогает кому-то.
Ответ 23
Я пишу помощник bash, чтобы написать хороший инструмент bash
project home: https://gitlab.mbedsys.org/mbedsys/bashopts
Пример:
#!/bin/bash -ei
# load the library
. bashopts.sh
# Enable backtrace dusplay on error
trap 'bashopts_exit_handle' ERR
# Initialize the library
bashopts_setup -n "$0" -d "This is myapp tool description displayed on help message" -s "$HOME/.config/myapprc"
# Declare the options
bashopts_declare -n first_name -l first -o f -d "First name" -t string -i -s -r
bashopts_declare -n last_name -l last -o l -d "Last name" -t string -i -s -r
bashopts_declare -n display_name -l display-name -t string -d "Display name" -e "\$first_name \$last_name"
bashopts_declare -n age -l number -d "Age" -t number
bashopts_declare -n email_list -t string -m add -l email -d "Email adress"
# Parse arguments
bashopts_parse_args "[email protected]"
# 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:"")
наслаждайтесь:)
Ответ 24
Вот мой подход - с помощью regexp.
- no getopts
- он обрабатывает блок коротких параметров
-qwerty
- он обрабатывает короткие параметры
-q -w -e
- он обрабатывает длинные параметры
--qwerty
- вы можете передать атрибут короткому или длинному варианту (если вы используете блок коротких опций, атрибут привязан к последней опции)
- вы можете использовать пробелы или
=
для предоставления атрибутов, но атрибут соответствует до тех пор, пока не встретит дефис + пробел "разделитель", поэтому в --q=qwe ty
qwe ty
есть один атрибут
- он обрабатывает смешение всего выше, поэтому
-o a -op attr ibute --option=att ribu te --op-tion attribute --option att-ribute
действительно
script:
#!/usr/bin/env sh
help_menu() {
echo "Usage:
${0##*/} [-h][-l FILENAME][-d]
Options:
-h, --help
display this help and exit
-l, --logfile=FILENAME
filename
-d, --debug
enable debug
"
}
parse_options() {
case $opt in
h|help)
help_menu
exit
;;
l|logfile)
logfile=${attr}
;;
d|debug)
debug=true
;;
*)
echo "Unknown option: ${opt}\nRun ${0##*/} -h for help.">&2
exit 1
esac
}
[email protected]
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
Ответ 25
Используйте "аргументы" модуля из bash-modules
Пример:
#!/bin/bash
. import.sh log arguments
NAME="world"
parse_arguments "-n|--name)NAME;S" -- "[email protected]" || {
error "Cannot parse command line."
exit 1
}
info "Hello, $NAME!"
Ответ 26
Это также может быть полезно знать, вы можете установить значение, и если кто-то предоставляет ввод, переопределите значение по умолчанию с этим значением.
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}"
Ответ 27
Вот мое улучшенное решение Bruno Bronosky, используя переменные массивы.
он позволяет вам смешивать положение параметров и давать вам массив параметров, сохраняющий порядок без параметров
#!/bin/bash
echo [email protected]
PARAMS=()
SOFT=0
SKIP=()
for i in "[email protected]"
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
Ответ 28
Другое решение без getopt [s], POSIX, старый стиль Unix
Похоже на решение Bruno Bronosky опубликовано, это здесь без использования getopt(s)
.
Основная отличительная особенность моего решения заключается в том, что он позволяет объединять параметры, как tar -xzf foo.tar.gz
, равно tar -x -z -f foo.tar.gz
. И так же, как в tar
, ps
и т.д. Ведущий дефис является необязательным для блока коротких опций (но это можно легко изменить). Также поддерживаются длинные параметры (но когда блок начинается с одного, тогда требуются два ведущих дефиса).
Код с примерами параметров
#!/bin/sh
echo
echo "POSIX-compliant getopt(s)-free old-style-supporting option parser from [email protected][se.unix]"
echo
print_usage() {
echo "Usage:
$0 {a|b|c} [ARG...]
Options:
--aaa-0-args
-a
Option without arguments.
--bbb-1-args ARG
-b ARG
Option with one argument.
--ccc-2-args ARG1 ARG2
-c ARG1 ARG2
Option with two arguments.
" >&2
}
if [ $# -le 0 ]; then
print_usage
exit 1
fi
opt=
while :; do
if [ $# -le 0 ]; then
# no parameters remaining -> end option parsing
break
elif [ ! "$opt" ]; then
# we are at the beginning of a fresh block
# remove optional leading hyphen and strip trailing whitespaces
opt=$(echo "$1" | sed 's/^-\?\([a-zA-Z0-9\?-]*\)/\1/')
fi
# get the first character -> check whether long option
first_chr=$(echo "$opt" | awk '{print substr($1, 1, 1)}')
[ "$first_chr" = - ] && long_option=T || long_option=F
# note to write the options here with a leading hyphen less
# also do not forget to end short options with a star
case $opt in
-)
# end of options
shift
break
;;
a*|-aaa-0-args)
echo "Option AAA activated!"
;;
b*|-bbb-1-args)
if [ "$2" ]; then
echo "Option BBB with argument '$2' activated!"
shift
else
echo "BBB parameters incomplete!" >&2
print_usage
exit 1
fi
;;
c*|-ccc-2-args)
if [ "$2" ] && [ "$3" ]; then
echo "Option CCC with arguments '$2' and '$3' activated!"
shift 2
else
echo "CCC parameters incomplete!" >&2
print_usage
exit 1
fi
;;
h*|\?*|-help)
print_usage
exit 0
;;
*)
if [ "$long_option" = T ]; then
opt=$(echo "$opt" | awk '{print substr($1, 2)}')
else
opt=$first_chr
fi
printf 'Error: Unknown option: "%s"\n' "$opt" >&2
print_usage
exit 1
;;
esac
if [ "$long_option" = T ]; then
# if we had a long option then we are going to get a new block next
shift
opt=
else
# if we had a short option then just move to the next character
opt=$(echo "$opt" | awk '{print substr($1, 2)}')
# if block is now empty then shift to the next one
[ "$opt" ] || shift
fi
done
echo "Doing something..."
exit 0
Пример использования примера приведен ниже.
Позиция опций с аргументами
В чем его ценность, параметры с аргументами не являются последними (должны быть только длинные опции). Таким образом, пока, например, в tar
(по крайней мере, в некоторых реализациях) параметры f
должны быть последними, потому что имя файла следует (tar xzf bar.tar.gz
работает, но tar xfz bar.tar.gz
) это не так (см. более поздние примеры).
Несколько опций с аргументами
В качестве еще одного бонуса параметры параметра потребляются в порядке параметров по параметрам с требуемыми параметрами. Просто посмотрите на вывод моего script здесь с командной строкой abc X Y Z
(или -abc X Y Z
):
Option AAA activated!
Option BBB with argument 'X' activated!
Option CCC with arguments 'Y' and 'Z' activated!
Длинные опции также объединены
Кроме того, вы также можете иметь длинные опции в блоке параметров, учитывая, что они встречаются последними в блоке. Таким образом, следующие командные строки эквивалентны (включая порядок, в котором обрабатываются параметры и его аргументы):
-
-cba Z Y X
-
cba Z Y X
-
-cb-aaa-0-args Z Y X
-
-c-bbb-1-args Z Y X -a
-
--ccc-2-args Z Y -ba X
-
c Z Y b X a
-
-c Z Y -b X -a
-
--ccc-2-args Z Y --bbb-1-args X --aaa-0-args
Все это приводит к:
Option CCC with arguments 'Z' and 'Y' activated!
Option BBB with argument 'X' activated!
Option AAA activated!
Doing something...
Не в этом решении
Дополнительные аргументы
Параметры с необязательными аргументами должны быть возможны с небольшим количеством работы, например. глядя вперед, есть ли блок без дефиса; пользователь должен будет поместить дефис перед каждым блоком после блока с параметром, имеющим необязательный параметр. Может быть, это слишком сложно для общения с пользователем, поэтому лучше всего в этом случае просто взять ведущий дефис.
Все становится еще сложнее с несколькими возможными параметрами. Я бы посоветовал не делать параметры, пытаясь быть умными, определяя, может ли для него аргумент или нет (например, с опцией просто принимает число как необязательный аргумент), потому что это может сломаться в будущем.
Я лично предпочитаю дополнительные опции вместо необязательных аргументов.
Опционные аргументы, введенные с знаком равенства
Как и с необязательными аргументами, я не поклонник этого (BTW, есть ли тема для обсуждения плюсов и минусов разных стилей параметров?), но если вы этого хотите, вы, вероятно, могли бы реализовать его сами, как это сделано в http://mywiki.wooledge.org/BashFAQ/035#Manual_loop с оператором case --long-with-arg=?*
, а затем снятие знака равенства (это BTW сайт, в котором говорится, что выполнение конкатенации параметров возможно с помощью некоторые усилия, но "оставили [это] упражнение для читателя", что заставило меня взять их по их слову, но я начал с нуля).
Другие примечания
POSIX-совместимый, работает даже на старых настройках Busybox, с которыми мне пришлось иметь дело (например, cut
, head
и getopts
).
Ответ 29
Верхний ответ на этот вопрос казался немного затруднительным, когда я его попробовал - вот мое решение, которое я нашел более надежным:
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
Ответ 30
Простые и легко изменяемые параметры могут быть в любом порядке. это можно изменить, чтобы получить параметры в любой форме (-a, - -a, a и т.д.).
for arg in "[email protected]"
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