while getopts "hd:R:" arg; do
case $arg in
h)
echo "usgae"
;;
d)
dir=$OPTARG
;;
R)
if [[ $OPTARG =~ ^[0-9]+$ ]];then
level=$OPTARG
else
level=1
fi
;;
\?)
echo "WRONG" >&2
;;
esac
done
级别是指-R
的参数,dir是指-d
的参数
当我输入./count.sh -R 1 -d test/
时,它正常工作
当我输入./count.sh -d test/ -R 1
时,它正常工作
但我想在输入./count.sh -d test/ -R
或./count.sh -R -d test/
这意味着我希望-R
具有默认值,并且命令序列可以更灵活。
答案 0 :(得分:20)
错误。实际上getopts
确实支持可选参数!从bash手册页:
If a required argument is not found, and getopts is not silent,
a question mark (?) is placed in name, OPTARG is unset, and a diagnostic
message is printed. If getopts is silent, then a colon (:) is placed in name
and OPTARG is set to the option character found.
当手册页显示“无声”时,表示无声错误报告。要启用它, optstring 的第一个字符必须是冒号:
while getopts ":hd:R:" arg; do
# ...rest of iverson's loop should work as posted
done
由于Bash的 getopt 无法识别--
以结束选项列表,因此当-R
是最后一个选项时,它可能无效,后跟一些路径参数。
P.S。:传统上, getopt.c 使用两个冒号(::
)来指定可选参数。但是,Bash使用的版本没有。
答案 1 :(得分:15)
getopts
并不真正支持这一点;但是写自己的替代品并不难。
while true; do
case $1 in
-R) level=1
shift
case $1 in
*[!0-9]* | "") ;;
*) level=$1; shift ;;
esac ;;
# ... Other options ...
-*) echo "$0: Unrecognized option $1" >&2
exit 2;;
*) break ;;
esac
done
答案 2 :(得分:10)
我同意tripleee,getopts不支持可选参数处理。
我已经解决的妥协解决方案是使用相同选项标志的大写/小写组合来区分采用参数的选项和不参数的选项。
示例:强> 的
COMMAND_LINE_OPTIONS_HELP='
Command line options:
-I Process all the files in the default dir: '`pwd`'/input/
-i DIR Process all the files in the user specified input dir
-h Print this help menu
Examples:
Process all files in the default input dir
'`basename $0`' -I
Process all files in the user specified input dir
'`basename $0`' -i ~/my/input/dir
'
VALID_COMMAND_LINE_OPTIONS="i:Ih"
INPUT_DIR=
while getopts $VALID_COMMAND_LINE_OPTIONS options; do
#echo "option is " $options
case $options in
h)
echo "$COMMAND_LINE_OPTIONS_HELP"
exit $E_OPTERROR;
;;
I)
INPUT_DIR=`pwd`/input
echo ""
echo "***************************"
echo "Use DEFAULT input dir : $INPUT_DIR"
echo "***************************"
;;
i)
INPUT_DIR=$OPTARG
echo ""
echo "***************************"
echo "Use USER SPECIFIED input dir : $INPUT_DIR"
echo "***************************"
;;
\?)
echo "Usage: `basename $0` -h for help";
echo "$COMMAND_LINE_OPTIONS_HELP"
exit $E_OPTERROR;
;;
esac
done
答案 3 :(得分:8)
这实际上非常简单。只需在R之后删除尾部冒号并使用OPTIND
while getopts "hRd:" opt; do
case $opt in
h) echo -e $USAGE && exit
;;
d) DIR="$OPTARG"
;;
R)
if [[ ${@:$OPTIND} =~ ^[0-9]+$ ]];then
LEVEL=${@:$OPTIND}
OPTIND=$((OPTIND+1))
else
LEVEL=1
fi
;;
\?) echo "Invalid option -$OPTARG" >&2
;;
esac
done
echo $LEVEL $DIR
count.sh -d test
测试
count.sh -d test -R
1次测试
count.sh -R -d test
1次测试
count.sh -d test -R 2
2测试
count.sh -R 2 -d test
2测试
答案 4 :(得分:1)
我受到@calandoa的answer(唯一的实际可行!)的启发,我做了一个简单的函数,可以轻松地多次使用。
getopts_get_optional_argument() {
eval next_token=\${$OPTIND}
if [[ -n $next_token && $next_token != -* ]]; then
OPTIND=$((OPTIND + 1))
OPTARG=$next_token
else
OPTARG=""
fi
}
用法示例:
while getopts "hdR" option; do
case $option in
d)
getopts_get_optional_argument $@
dir=${OPTARG}
;;
R)
getopts_get_optional_argument $@
level=${OPTARG:-1}
;;
h)
show_usage && exit 0
;;
\?)
show_usage && exit 1
;;
esac
done
这为我们提供了一种实用的方法来获取getopts
中的“缺少特征” :)
注意,尽管如此,带有可选参数的命令行选项似乎仍然是discouraged explicitly
准则7:选项参数不应是可选的。
但是,如果没有这种方法,我就没有直观的方式来实现我的情况:我有2个模式,可以通过使用一个标志或其他标志来激活,并且这两种模式都有一个带有默认缺省值的参数。引入第三个标志只是为了消除歧义,使其看起来像不良的CLI样式。
我已经通过多种组合对它进行了测试,包括@ aaron-sua的所有答案,并且效果很好。
答案 5 :(得分:0)
尝试:
while getopts "hd:R:" arg; do
case $arg in
h)
echo "usage"
;;
d)
dir=$OPTARG
;;
R)
if [[ $OPTARG =~ ^[0-9]+$ ]];then
level=$OPTARG
elif [[ $OPTARG =~ ^-. ]];then
level=1
let OPTIND=$OPTIND-1
else
level=1
fi
;;
\?)
echo "WRONG" >&2
;;
esac
done
我认为上述代码在您使用getopts
时仍可用于您的目的。当getopts
遇到-R
时,我已将以下三行添加到您的代码中:
elif [[ $OPTARG =~ ^-. ]];then
level=1
let OPTIND=$OPTIND-1
如果遇到-R
并且第一个参数看起来像另一个getopts参数,则level将设置为默认值1
,然后$OPTIND
变量减1。下一次getopts
去抓一个参数时,它会抓取正确的参数而不是跳过它。
以下是基于Jan Schampera's comment at this tutorial代码的类似示例:
#!/bin/bash
while getopts :abc: opt; do
case $opt in
a)
echo "option a"
;;
b)
echo "option b"
;;
c)
echo "option c"
if [[ $OPTARG = -* ]]; then
((OPTIND--))
continue
fi
echo "(c) argument $OPTARG"
;;
\?)
echo "WTF!"
exit 1
;;
esac
done
当您发现OPTARG von -c以连字符开头时,请重置OPTIND并重新运行getopts(继续while循环)。哦,当然,这并不完美,需要更强大的功能。这只是一个例子。
答案 6 :(得分:0)
以下代码通过检查前导破折号来解决此问题,如果找到则减少OPTIND以指回要跳过的处理选项。这通常可以正常工作,除非您不知道用户将在命令行上放置选项的顺序 - 如果您的可选参数选项是最后一个并且不提供参数,则getopts将希望错误输出。
为了解决缺少最后一个参数的问题,“$ @”数组只是附加一个空字符串“$ @”,这样getopts就会满足它已经吞噬了另一个选项参数。为了修复这个新的空参数,设置了一个变量来保存要处理的所有选项的总数 - 当处理最后一个选项时,调用一个名为trim的辅助函数,并在使用该值之前删除空字符串。
这不是工作代码,它只有占位符,但您可以轻松修改它,并且稍加注意,构建一个健壮的系统会很有用。
#!/usr/bin/env bash
declare -r CHECK_FLOAT="%f"
declare -r CHECK_INTEGER="%i"
## <arg 1> Number - Number to check
## <arg 2> String - Number type to check
## <arg 3> String - Error message
function check_number() {
local NUMBER="${1}"
local NUMBER_TYPE="${2}"
local ERROR_MESG="${3}"
local FILTERED_NUMBER=$(sed 's/[^.e0-9+\^]//g' <<< "${NUMBER}")
local -i PASS=1
local -i FAIL=0
if [[ -z "${NUMBER}" ]]; then
echo "Empty number argument passed to check_number()." 1>&2
echo "${ERROR_MESG}" 1>&2
echo "${FAIL}"
elif [[ -z "${NUMBER_TYPE}" ]]; then
echo "Empty number type argument passed to check_number()." 1>&2
echo "${ERROR_MESG}" 1>&2
echo "${FAIL}"
elif [[ ! "${#NUMBER}" -eq "${#FILTERED_NUMBER}" ]]; then
echo "Non numeric characters found in number argument passed to check_number()." 1>&2
echo "${ERROR_MESG}" 1>&2
echo "${FAIL}"
else
case "${NUMBER_TYPE}" in
"${CHECK_FLOAT}")
if ((! $(printf "${CHECK_FLOAT}" "${NUMBER}" &>/dev/random;echo $?))); then
echo "${PASS}"
else
echo "${ERROR_MESG}" 1>&2
echo "${FAIL}"
fi
;;
"${CHECK_INTEGER}")
if ((! $(printf "${CHECK_INTEGER}" "${NUMBER}" &>/dev/random;echo $?))); then
echo "${PASS}"
else
echo "${ERROR_MESG}" 1>&2
echo "${FAIL}"
fi
;;
*)
echo "Invalid number type format: ${NUMBER_TYPE} to check_number()." 1>&2
echo "${FAIL}"
;;
esac
fi
}
## Note: Number can be any printf acceptable format and includes leading quotes and quotations,
## and anything else that corresponds to the POSIX specification.
## E.g. "'1e+03" is valid POSIX float format, see http://mywiki.wooledge.org/BashFAQ/054
## <arg 1> Number - Number to print
## <arg 2> String - Number type to print
function print_number() {
local NUMBER="${1}"
local NUMBER_TYPE="${2}"
case "${NUMBER_TYPE}" in
"${CHECK_FLOAT}")
printf "${CHECK_FLOAT}" "${NUMBER}" || echo "Error printing Float in print_number()." 1>&2
;;
"${CHECK_INTEGER}")
printf "${CHECK_INTEGER}" "${NUMBER}" || echo "Error printing Integer in print_number()." 1>&2
;;
*)
echo "Invalid number type format: ${NUMBER_TYPE} to print_number()." 1>&2
;;
esac
}
## <arg 1> String - String to trim single ending whitespace from
function trim_string() {
local STRING="${1}"
echo -En $(sed 's/ $//' <<< "${STRING}") || echo "Error in trim_string() expected a sensible string, found: ${STRING}" 1>&2
}
## This a hack for getopts because getopts does not support optional
## arguments very intuitively. E.g. Regardless of whether the values
## begin with a dash, getopts presumes that anything following an
## option that takes an option argument is the option argument. To fix
## this the index variable OPTIND is decremented so it points back to
## the otherwise skipped value in the array option argument. This works
## except for when the missing argument is on the end of the list,
## in this case getopts will not have anything to gobble as an
## argument to the option and will want to error out. To avoid this an
## empty string is appended to the argument array, yet in so doing
## care must be taken to manage this added empty string appropriately.
## As a result any option that doesn't exit at the time its processed
## needs to be made to accept an argument, otherwise you will never
## know if the option will be the last option sent thus having an empty
## string attached and causing it to land in the default handler.
function process_options() {
local OPTIND OPTERR=0 OPTARG OPTION h d r s M R S D
local ERROR_MSG=""
local OPTION_VAL=""
local EXIT_VALUE=0
local -i NUM_OPTIONS
let NUM_OPTIONS=${#@}+1
while getopts “:h?d:DM:R:S:s:r:” OPTION "$@";
do
case "$OPTION" in
h)
help | more
exit 0
;;
r)
OPTION_VAL=$(((${NUM_OPTIONS}==${OPTIND})) && trim_string "${OPTARG##*=}" || echo -En "${OPTARG##*=}")
ERROR_MSG="Invalid input: Integer or floating point number required."
if [[ -z "${OPTION_VAL}" ]]; then
## can set global flags here
:;
elif [[ "${OPTION_VAL}" =~ ^-. ]]; then
let OPTIND=${OPTIND}-1
## can set global flags here
elif [ "${OPTION_VAL}" = "0" ]; then
## can set global flags here
:;
elif (($(check_number "${OPTION_VAL}" "${CHECK_FLOAT}" "${ERROR_MSG}"))); then
:; ## do something really useful here..
else
echo "${ERROR_MSG}" 1>&2 && exit -1
fi
;;
d)
OPTION_VAL=$(((${NUM_OPTIONS}==${OPTIND})) && trim_string "${OPTARG##*=}" || echo -En "${OPTARG##*=}")
[[ ! -z "${OPTION_VAL}" && "${OPTION_VAL}" =~ ^-. ]] && let OPTIND=${OPTIND}-1
DEBUGMODE=1
set -xuo pipefail
;;
s)
OPTION_VAL=$(((${NUM_OPTIONS}==${OPTIND})) && trim_string "${OPTARG##*=}" || echo -En "${OPTARG##*=}")
if [[ ! -z "${OPTION_VAL}" && "${OPTION_VAL}" =~ ^-. ]]; then ## if you want a variable value that begins with a dash, escape it
let OPTIND=${OPTIND}-1
else
GLOBAL_SCRIPT_VAR="${OPTION_VAL}"
:; ## do more important things
fi
;;
M)
OPTION_VAL=$(((${NUM_OPTIONS}==${OPTIND})) && trim_string "${OPTARG##*=}" || echo -En "${OPTARG##*=}")
ERROR_MSG=$(echo "Error - Invalid input: ${OPTION_VAL}, Integer required"\
"retry with an appropriate option argument.")
if [[ -z "${OPTION_VAL}" ]]; then
echo "${ERROR_MSG}" 1>&2 && exit -1
elif [[ "${OPTION_VAL}" =~ ^-. ]]; then
let OPTIND=${OPTIND}-1
echo "${ERROR_MSG}" 1>&2 && exit -1
elif (($(check_number "${OPTION_VAL}" "${CHECK_INTEGER}" "${ERROR_MSG}"))); then
:; ## do something useful here
else
echo "${ERROR_MSG}" 1>&2 && exit -1
fi
;;
R)
OPTION_VAL=$(((${NUM_OPTIONS}==${OPTIND})) && trim_string "${OPTARG##*=}" || echo -En "${OPTARG##*=}")
ERROR_MSG=$(echo "Error - Invalid option argument: ${OPTION_VAL},"\
"the value supplied to -R is expected to be a "\
"qualified path to a random character device.")
if [[ -z "${OPTION_VAL}" ]]; then
echo "${ERROR_MSG}" 1>&2 && exit -1
elif [[ "${OPTION_VAL}" =~ ^-. ]]; then
let OPTIND=${OPTIND}-1
echo "${ERROR_MSG}" 1>&2 && exit -1
elif [[ -c "${OPTION_VAL}" ]]; then
:; ## Instead of erroring do something useful here..
else
echo "${ERROR_MSG}" 1>&2 && exit -1
fi
;;
S)
STATEMENT=$(((${NUM_OPTIONS}==${OPTIND})) && trim_string "${OPTARG##*=}" || echo -En "${OPTARG##*=}")
ERROR_MSG="Error - Default text string to set cannot be empty."
if [[ -z "${STATEMENT}" ]]; then
## Instead of erroring you could set a flag or do something else with your code here..
elif [[ "${STATEMENT}" =~ ^-. ]]; then ## if you want a statement that begins with a dash, escape it
let OPTIND=${OPTIND}-1
echo "${ERROR_MSG}" 1>&2 && exit -1
echo "${ERROR_MSG}" 1>&2 && exit -1
else
:; ## do something even more useful here you can modify the above as well
fi
;;
D)
## Do something useful as long as it is an exit, it is okay to not worry about the option arguments
exit 0
;;
*)
EXIT_VALUE=-1
;&
?)
usage
exit ${EXIT_VALUE}
;;
esac
done
}
process_options "$@ " ## extra space, so getopts can find arguments
答案 7 :(得分:0)
您始终可以决定将选项与小写或大写区分开来。
但是我的想法是两次调用getopts
并且第一次解析而不用忽略它们的参数(R
)然后第二次只解析带有参数支持的选项(R:
)。唯一的技巧是在处理期间需要更改OPTIND
(索引),因为它保持指向当前参数的指针。
以下是代码:
#!/usr/bin/env bash
while getopts ":hd:R" arg; do
case $arg in
d) # Set directory, e.g. -d /foo
dir=$OPTARG
;;
R) # Optional level value, e.g. -R 123
OI=$OPTIND # Backup old value.
((OPTIND--)) # Decrease argument index, to parse -R again.
while getopts ":R:" r; do
case $r in
R)
# Check if value is in numeric format.
if [[ $OPTARG =~ ^[0-9]+$ ]]; then
level=$OPTARG
else
level=1
fi
;;
:)
# Missing -R value.
level=1
;;
esac
done
[ -z "$level" ] && level=1 # If value not found, set to 1.
OPTIND=$OI # Restore old value.
;;
\? | h | *) # Display help.
echo "$0 usage:" && grep " .)\ #" $0
exit 0
;;
esac
done
echo Dir: $dir
echo Level: $level
以下是几个有效的场景测试:
$ ./getopts.sh -h
./getopts.sh usage:
d) # Set directory, e.g. -d /foo
R) # Optional level value, e.g. -R 123
\? | h | *) # Display help.
$ ./getopts.sh -d /foo
Dir: /foo
Level:
$ ./getopts.sh -d /foo -R
Dir: /foo
Level: 1
$ ./getopts.sh -d /foo -R 123
Dir: /foo
Level: 123
$ ./getopts.sh -d /foo -R wtf
Dir: /foo
Level: 1
$ ./getopts.sh -R -d /foo
Dir: /foo
Level: 1
不起作用的场景(因此代码需要更多调整):
$ ./getopts.sh -R 123 -d /foo
Dir:
Level: 123
有关getopts
用法的更多信息,请参阅man bash
。
另见:Bash Hackers Wiki的Small getopts tutorial
答案 8 :(得分:0)
此解决方案定义了&#39; R&#39;没有参数(没有&#39;:&#39;),在&#39; -R&#39;之后测试任何参数。 (管理命令行上的最后一个选项)并测试现有参数是否以短划线开头。
# No : after R
while getopts "hd:R" arg; do
case $arg in
(...)
R)
# Check next positional parameter
eval nextopt=\${$OPTIND}
# existing or starting with dash?
if [[ -n $nextopt && $nextopt != -* ]] ; then
OPTIND=$((OPTIND + 1))
level=$nextopt
else
level=1
fi
;;
(...)
esac
done
答案 9 :(得分:0)
我自己碰到了这个问题,觉得现有的解决方案都不是真的干净。经过一番尝试并尝试了各种方法之后,我发现利用:) ...
与getopts一起使用SILENT模式似乎可以达到使OPTIND保持同步的目的。
usage: test.sh [-abst] [-r [DEPTH]] filename
*NOTE: -r (recursive) with no depth given means full recursion
#!/usr/bin/env bash
depth='-d 1'
while getopts ':abr:st' opt; do
case "${opt}" in
a) echo a;;
b) echo b;;
r) if [[ "${OPTARG}" =~ ^[0-9]+$ ]]; then
depth="-d ${OPTARG}"
else
depth=
(( OPTIND-- ))
fi
;;
s) echo s;;
t) echo t;;
:) [[ "${OPTARG}" = 'r' ]] && depth=;;
*) echo >&2 "Invalid option: ${opt}"; exit 1;;
esac
done
shift $(( OPTIND - 1 ))
filename="$1"
...
答案 10 :(得分:0)
我认为有两种方法。
首先是calandoa的回答,使用OPTIND,并且没有静默模式。
第二个是使用OPTIND和静音模式。
while getopts ":Rr:" name; do
case ${name} in
R)
eval nextArg=\${$OPTIND}
# check option followed by nothing or other option.
if [[ -z ${nextArg} || $nextArg =~ ^-.* ]]; then
level=1
elif [[ $nextArg =~ ^[0-9]+$ ]]; then
level=$nextArg
OPTIND=$((OPTIND + 1))
else
level=1
fi
;;
r)
# check option followed by other option.
if [[ $OPTARG =~ ^-.* ]]; then
OPTIND=$((OPTIND - 1))
level2=2
elif [[ $OPTARG =~ ^[0-9]+$ ]]; then
level2="$OPTARG"
else
level2=2
fi
;;
:)
# check no argument
case $OPTARG in
r)
level2=2
;;
esac
esac
done
echo "Level 1 : $level"
echo "Level 2 : $level2"
答案 11 :(得分:0)
目前提出的所有解决方案都将代码放在 case ... in ... esac
中,但在我看来,修改 getopts
命令会更自然,因此我编写了这个函数:
编辑:
现在,您可以指定可选参数的类型(请参阅用法信息)。
此外,该函数现在不再测试 $nextArg
是否“看起来像”一个选项 arg,而是检查 $nextArg
是否包含来自 $optstring
的字母。
这样,$optstring
中未包含的选项字母可以用作可选参数,就像 getopts
' 强制参数一样。
最新变化:
$nextArg
是否为选项参数的测试:$nextArg
是否以破折号开头。$optstring
的内容不被识别。$nextArg
是否为整数。用法:
调用:getopts-plus optstring name "$@"
optstring
:与普通的 getopts 一样,但您可以通过将 :: 附加到选项字母来指定带有可选参数的选项。
但是,如果您的脚本支持使用可选参数作为唯一选项参数的选项的调用,后跟非选项参数,则非选项参数将被视为选项的参数。
如果幸运的话,可选参数应该是整数,而非选项参数是字符串,反之亦然,您可以通过附加 :::i 来指定类型用于整数或 :::s 用于解决该问题的字符串。
如果这不适用,您可以通过将 ::/.../ 附加到选项字母来为可选参数指定正则表达式。
如果在带有可选参数的选项后面有一个非可选参数,则只有匹配正则表达式才被认为是可选参数。
需要明确的是:::/.../ 不用于参数验证,而仅用于区分具有可选参数和非选项参数的选项的参数。
#!/bin/bash
# Invocation: getopts-plus optstring name "$@"\
# \
# optstring: Like normal getopts, but you may specify options with optional argument
# by appending :: to the option letter.\
# \
# However, if your script supports an invocation with an option with optional
# argument as the only option argument, followed by a non-option argument,
# the non-option argument will be considered to be the argument for the option.\
# \
# If you're lucky and the optional argument is expected to be an integer, whereas
# the non-option argument is a string or vice versa, you may specify the type by
# appending :::i for an integer or :::s for a string to solve that issue.\
# \
# If that doesn't apply, you may specify a regexp for the optional arg by appending
# ::/.../ to the option letter.\
# If there is a non-option argument after the option with optional argument, it will
# be considered to be the optional argument only if it matches the regexp.\
# To be clear: ::/.../ is not meant for argument validation but solely to discriminate
# between arguments for options with optional argument and non-option arguments.
function getopts-plus
{
local optstring=$1
local -n name=$2
shift 2
local optionalArgSuffixRE='::(:[si]|/.*/)?'
local optionalArgTypeCaptureRE=':::([si])|::(/.*/)'
# If we pass 'opt' for 'name' (as I always do when using getopts) and there is
# also a local variable 'opt', the "outer" 'opt' will always be empty.
# I don't understand why a local variable interferes with caller's variable with
# same name in this case; however, we can easily circumvent this.
local opt_
# Extract options with optional arg
local -A isOptWithOptionalArg
while read opt_; do
# Using an associative array as set
isOptWithOptionalArg[$opt_]=1
done <<<$(grep -Eo "[a-zA-Z]$optionalArgSuffixRE" <<<$optstring | sed -E "s#$optionalArgSuffixRE##")
# Extract all option letters (used to weed out possible optional args that are option args)
local optLetters=$(sed -E "s#:|$optionalArgSuffixRE##g" <<<$optstring | grep -o '[A-Za-z]')
# Save original optstring, then remove our suffix(es)
local optstringOrg=$optstring
optstring=$(sed -E "s#$optionalArgSuffixRE##g" <<<$optstring)
getopts $optstring name "$@" || return # Return value is getopts' exit value.
# If current option is an option with optional arg and if an arg has been provided,
# check if that arg is not an option and if it isn't, check if that arg matches(*)
# the specified type, if any, and if it does or no type has been specified,
# assign it to OPTARG and inc OPTIND.
#
# (*) We detect an int because it's easy, but we assume a string if it's not an int
# because detecting a string would be complicated.
# So it sounds strange to call it a match if we know that the optional arg is specified
# to be a string, but merely that the provided arg is not an int, but in this context,
# "not an int" is equivalent to "string". At least I think so, but I might be wrong.
if ((isOptWithOptionalArg[$name])) && [[ ${!OPTIND} ]]; then
local nextArg=${!OPTIND} foundOpt=0
# Test if $nextArg is an option arg
if [[ $nextArg == -* ]]; then
# Check if $nextArg contains a letter from $optLetters.
# This way, an option not contained in $optstring can be
# used as optional arg, as with getopts' mandatory args.
local i
# Start at char 1 to skip the leading dash
for ((i = 1; i < ${#nextArg}; i++)); do
while read opt_; do
[[ ${nextArg:i:1} == $opt_ ]] && foundOpt=1 && break 2
done <<<$optLetters
done
((foundOpt)) && return
fi
# Extract type of optional arg if specified
# N.B.: We use -n option and p modifier to get an output
# only if the substitution has actually been performed.
local optArgType=$(sed -En "s#.*$name($optionalArgTypeCaptureRE).*#\2\3#p" <<<$optstringOrg)
local nextArgIsOptArg=0
case $optArgType in
/*/) # Check if $nextArg matches regexp
local optArgRE=$(sed -E "s#/(.*)/#\1#" <<<$optArgType)
[[ $nextArg =~ $optArgRE ]] && nextArgIsOptArg=1
;;
[si]) # Check if $nextArg is an int
local nextArgIsInt=0
[[ $nextArg =~ ^[0-9]+$ ]] && nextArgIsInt=1
# Test if specified type and arg type match (see (*) above).
# N.B.: We need command groups since && and || between commands have same precedence.
{ [[ $optArgType == i ]] && ((nextArgIsInt)) || { [[ $optArgType == s ]] && ((! nextArgIsInt)); }; } && nextArgIsOptArg=1
;;
'') # No type or regexp specified => Assume $nextArg is optional arg.
nextArgIsOptArg=1
;;
esac
if ((nextArgIsOptArg)); then
OPTARG=$nextArg && ((OPTIND++))
fi
fi
}
没有注释的相同功能,以防您不需要它们:
#!/bin/bash
# Invocation: getopts-plus optstring name "$@"\
# \
# optstring: Like normal getopts, but you may specify options with optional argument
# by appending :: to the option letter.\
# \
# However, if your script supports an invocation with an option with optional
# argument as the only option argument, followed by a non-option argument,
# the non-option argument will be considered to be the argument for the option.\
# \
# If you're lucky and the optional argument is expected to be an integer, whereas
# the non-option argument is a string or vice versa, you may specify the type by
# appending :::i for an integer or :::s for a string to solve that issue.\
# \
# If that doesn't apply, you may specify a regexp for the optional arg by appending
# ::/.../ to the option letter.\
# If there is a non-option argument after the option with optional argument, it will
# be considered to be the optional argument only if it matches the regexp.\
# To be clear: ::/.../ is not meant for argument validation but solely to discriminate
# between arguments for options with optional argument and non-option arguments.
function getopts-plus
{
local optstring=$1
local -n name=$2
shift 2
local optionalArgSuffixRE='::(:[si]|/.*/)?'
local optionalArgTypeCaptureRE=':::([si])|::(/.*/)'
local opt_
local -A isOptWithOptionalArg
while read opt_; do
isOptWithOptionalArg[$opt_]=1
done <<<$(grep -Eo "[a-zA-Z]$optionalArgSuffixRE" <<<$optstring | sed -E "s#$optionalArgSuffixRE##")
local optLetters=$(sed -E "s#:|$optionalArgSuffixRE##g" <<<$optstring | grep -o '[A-Za-z]')
local optstringOrg=$optstring
optstring=$(sed -E "s#$optionalArgSuffixRE##g" <<<$optstring)
getopts $optstring name "$@" || return
if ((isOptWithOptionalArg[$name])) && [[ ${!OPTIND} ]]; then
local nextArg=${!OPTIND} foundOpt=0
if [[ $nextArg == -* ]]; then
local i
for ((i = 1; i < ${#nextArg}; i++)); do
while read opt_; do
[[ ${nextArg:i:1} == $opt_ ]] && foundOpt=1 && break 2
done <<<$optLetters
done
((foundOpt)) && return
fi
local optArgType=$(sed -En "s#.*$name($optionalArgTypeCaptureRE).*#\2\3#p" <<<$optstringOrg)
local nextArgIsOptArg=0
case $optArgType in
/*/)
local optArgRE=$(sed -E "s#/(.*)/#\1#" <<<$optArgType)
[[ $nextArg =~ $optArgRE ]] && nextArgIsOptArg=1
;;
[si])
local nextArgIsInt=0
[[ $nextArg =~ ^[0-9]+$ ]] && nextArgIsInt=1
{ [[ $optArgType == i ]] && ((nextArgIsInt)) || { [[ $optArgType == s ]] && ((! nextArgIsInt)); }; } && nextArgIsOptArg=1
;;
'')
nextArgIsOptArg=1
;;
esac
if ((nextArgIsOptArg)); then
OPTARG=$nextArg && ((OPTIND++))
fi
fi
}
一些测试:
-g
的可选 arg 类型指定为整数,未传递 int 但后跟非选项字符串 arg。
$ . ./getopts-plus.sh
$ while getopts-plus 'b:c::de::f::g:::ia' opt -ab 99 -c 11 -def 55 -g "hello you"; do e opt OPTARG; echo; printf "%.0s-" $(seq 1 25); echo -e "\n"; done
opt == 'a'
OPTARG == ''
-------------------------
opt == 'b'
OPTARG == '99'
-------------------------
opt == 'c'
OPTARG == '11'
-------------------------
opt == 'd'
OPTARG == ''
-------------------------
opt == 'e'
OPTARG == ''
-------------------------
opt == 'f'
OPTARG == '55'
-------------------------
opt == 'g'
OPTARG == '' <-- Empty because "hello you" is not an int
同上,但使用 int arg。
$ OPTIND=1
$ while getopts-plus 'b:c::de::f::g:::ia' opt -ab 99 -c 11 -def 55 -g 7 "hello you"; do e opt OPTARG; echo; printf "%.0s-" $(seq 1 25); echo -e "\n"; done
opt == 'a'
OPTARG == ''
-------------------------
opt == 'b'
OPTARG == '99'
-------------------------
opt == 'c'
OPTARG == '11'
-------------------------
opt == 'd'
OPTARG == ''
-------------------------
opt == 'e'
OPTARG == ''
-------------------------
opt == 'f'
OPTARG == '55'
-------------------------
opt == 'g'
OPTARG == '7' <-- The passed int
添加了可选选项 -h
和正则表达式 ^(a|b|ab|ba)$
,没有传递参数。
$ OPTIND=1
$ while getopts-plus 'b:c::de::f::g:::ih::/^(a|b|ab|ba)$/a' opt -ab 99 -c 11 -def 55 -gh "hello you"; do e opt OPTARG; echo; printf "%.0s-" $(seq 1 25); echo -e "\n"; done
opt == 'a'
OPTARG == ''
-------------------------
opt == 'b'
OPTARG == '99'
-------------------------
opt == 'c'
OPTARG == '11'
-------------------------
opt == 'd'
OPTARG == ''
-------------------------
opt == 'e'
OPTARG == ''
-------------------------
opt == 'f'
OPTARG == '55'
-------------------------
opt == 'g'
OPTARG == ''
-------------------------
opt == 'h'
OPTARG == '' <-- Empty because "hello you" does not match the regexp
像上面一样,但有一个与正则表达式匹配的参数。
$ OPTIND=1
$ while getopts-plus 'b:c::de::f::g:::ih::/^(a|b|ab|ba)$/a' opt -ab 99 -c 11 -def 55 -gh ab "hello you"; do e opt OPTARG; echo; printf "%.0s-" $(seq 1 25); echo -e "\n"; done
opt == 'a'
OPTARG == ''
-------------------------
opt == 'b'
OPTARG == '99'
-------------------------
opt == 'c'
OPTARG == '11'
-------------------------
opt == 'd'
OPTARG == ''
-------------------------
opt == 'e'
OPTARG == ''
-------------------------
opt == 'f'
OPTARG == '55'
-------------------------
opt == 'g'
OPTARG == ''
-------------------------
opt == 'h'
OPTARG == 'ab' <-- The arg that matches the regexp
答案 12 :(得分:0)
旧线程,但我想我还是会分享我所做的(这也大多比这个线程旧)。我已经厌倦了让 getopt 做我想做的事情,在一阵沮丧中这样做是为了支持带有可选参数的短选项和长选项。这是很长的路要走,肯定有人会笑,但它完全按照我的意愿工作 - 所有三种情况的过度评论示例如下:
#!/usr/bin/bash
# Begin testme.sh
shopt -s extglob;
VERSION="1.0"
function get_args(){
while test -n "${1}" ; do
case "${1}" in
-a | --all)
# dumb single argument example
PROCESS_ALL="yes"
shift 1
;;
-b | --buildnum)
# requires a second argument so use check_arg() below
check_arg $1 $2
BUILD_NUM=${2}
shift 2
;;
-c | --cache)
# Example where argument is not required, don't use check_arg()
if [ echo ${2} | grep -q "^-" ]; then
# no argument given, use default cache value
CACHEDIR=~/mycachedir
# Note: this could have been set upon entering the script
# and used the negative above as well
shift 1
else
cache=${2}
shift 2
fi
;;
-h | --help)
showhelp
exit 0
;;
-v | --version)
echo -e "$(basename ${0}) ${VERSION}\n"
exit 0
;;
# Handle getopt style short args (reason for shopts above)
-+([a-z,A-Z]))
# split up the arguments and call recursively with trailing break
arg="${1}"
newargs=$( echo ${1} | sed 's@-@@' | \
sed 's/.\{1\}/& /g' | \
sed 's/[^ ]* */-&/g')
newargs="${newargs} $(echo ${@} | sed "s@${arg}@@")"
get_args ${newargs}
break;
;;
*)
echo -e "Invalid argument ${1}!\n\n"
showhelp
exit 1
;;
esac
done
}
# Super lazy, but I didn't want an if/then/else for every required arg
function check_arg(){
if [ echo "${2}" | grep -q "^-" ]; then
echo "Error: $1 requires a valid argument."
exit 1
fi
}
function showhelp(){
echo ""
echo "`basename ${0}` is a utility to..."
}
# Process command line arguments
get_args $@
...
# End testme.sh
我从来没有遇到过,但我想可能有一种情况,我需要第二个参数以“-”字符开头,在这种情况下,我会在调用 get_args( )。我也使用 with 固定位置参数,在这种情况下,它们在最后,但解决方案相同。另外,我认为便携式版本只能处理 *) 中的组合短参数,但我认为如果 bash 的要求太高,则您需要自己解决。