bash 带有 getopts 的可选选项参数
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/11517139/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Optional option argument with getopts
提问by iverson
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
level refers to parameter of
-R
, dir refers to parameters of-d
when I input
./count.sh -R 1 -d test/
it works rightlywhen I input
./count.sh -d test/ -R 1
it works rightlybut I want to make it work when I input
./count.sh -d test/ -R
or./count.sh -R -d test/
level 指的是 的参数
-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/
This means that I want -R
has a default value and the sequence of command could be more flexible.
这意味着我想要-R
一个默认值并且命令的顺序可以更灵活。
采纳答案by tripleee
getopts
doesn't really support this; but it's not hard to write your own replacement.
getopts
并不真正支持这一点;但编写自己的替代品并不难。
while true; do
case in
-R) level=1
shift
case in
*[!0-9]* | "") ;;
*) level=; shift ;;
esac ;;
# ... Other options ...
-*) echo "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.
: Unrecognized option " >&2
exit 2;;
*) break ;;
esac
done
回答by Andreas Spindler
Wrong. Actually getopts
does support optional arguments! From the bash man page:
错误的。实际上getopts
确实支持可选参数!从 bash 手册页:
while getopts ":hd:R:" arg; do
# ...rest of iverson's loop should work as posted
done
When the man page says "silent" it means silent error reporting. To enable it, the first character of optstringneeds to be a colon:
当手册页说“silent”时,它意味着无提示错误报告。要启用它,optstring的第一个字符需要是一个冒号:
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 # 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
`' -I
Process all files in the user specified input dir
'`basename 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
`' -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 getopts_get_optional_argument() {
eval next_token=${$OPTIND}
if [[ -n $next_token && $next_token != -* ]]; then
OPTIND=$((OPTIND + 1))
OPTARG=$next_token
else
OPTARG=""
fi
}
` -h for help";
echo "$COMMAND_LINE_OPTIONS_HELP"
exit $E_OPTERROR;
;;
esac
done
Since Bash's getoptdoes not recognize --
to end the options list, it may not work when -R
is the last option, followed by some path argument.
由于 Bash 的getopt无法识别--
结束选项列表,因此当-R
是最后一个选项,后跟一些路径参数时,它可能不起作用。
P.S.: Traditionally, getopt.cuses two colons (::
) to specify an optional argument. However, the version used by Bash doesn't.
PS:传统上,getopt.c使用两个冒号 ( ::
) 来指定一个可选参数。但是,Bash 使用的版本没有。
回答by Akos Cz
I agree with tripleee, getopts does not support optional argument handling.
我同意三元组,getopts 不支持可选参数处理。
The compromised solution I have settled on is to use the upper case/lower case combination of the same option flag to differentiate between the option that takes an argument and the other that does not.
我已经确定的折衷解决方案是使用相同选项标志的大写/小写组合来区分接受参数的选项和不接受参数的选项。
Example:
例子:
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
回答by calandoa
This workaround defines 'R' with no argument (no ':'), tests for any argument after the '-R' (manage last option on the command line) and tests if an existing argument starts with a dash.
此解决方法定义不带参数的 'R'(无 ':'),测试 '-R' 之后的任何参数(管理命令行上的最后一个选项)并测试现有参数是否以破折号开头。
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=""
...
回答by Aaron Sua
This is actually pretty easy. Just drop the trailing colon after the R and use OPTIND
这实际上很容易。只需删除 R 后的尾随冒号并使用 OPTIND
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
count.sh -d test
count.sh -d 测试
test
测试
count.sh -d test -R
count.sh -d 测试 -R
1 test
1 次测试
count.sh -R -d test
count.sh -R -d 测试
1 test
1 次测试
count.sh -d test -R 2
count.sh -d 测试 -R 2
2 test
2 测试
count.sh -R 2 -d test
count.sh -R 2 -d 测试
2 test
2 测试
回答by nandilugio
Inspired in @calandoa's answer(the only one that actually works!), I've made a simple function that can make it easy to be used multiple times.
受到@calandoa 的回答(唯一真正有效的答案!)的启发,我制作了一个简单的函数,可以轻松多次使用。
elif [[ $OPTARG =~ ^-. ]];then
level=1
let OPTIND=$OPTIND-1
An example usage:
示例用法:
#!/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
This gives us a practical way to get "that missing feature"in getopts
:)
这为我们提供了一个实用的方法来获得“即缺少”在getopts
:)
NOTEthat nevertheless command-line options with optional args seems to be discouraged explicitly
请注意,尽管如此,似乎明确不鼓励带有可选参数的命令行选项
Guideline 7:Option-arguments should not be optional.
准则 7:选项参数不应该是可选的。
but I have no intuitive way to implement my case without this: I have 2 modes that are activated by either using one flag or other, and those have both an argument with a clear default. Introducing a third flag just to disambiguate makes it look a bad CLI style.
但是如果没有这个,我没有直观的方法来实现我的案例:我有 2 种模式,可以通过使用一个标志或其他标志来激活,并且这些模式都有一个具有明确默认值的参数。引入第三个标志只是为了消除歧义使它看起来很糟糕的 CLI 风格。
I've tested this with many combinations, including all in @aaron-sua's answer and works well.
我已经用多种组合对此进行了测试,包括@aaron-sua 的答案中的所有组合,并且效果很好。
回答by Terra
I just ran into this myself and felt that none of the existing solutions were really clean. After working on it a bit and trying various things, I found that leveraging getopts SILENT mode with :) ...
appears to have done the trick along with keeping OPTIND in sync.
我自己刚遇到这个问题,觉得现有的解决方案都不是真正干净的。在对其进行了一些研究并尝试了各种方法之后,我发现利用 getopts SILENT 模式:) ...
似乎已经完成了将 OPTIND 保持同步的技巧。
#!/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=""
local NUMBER_TYPE=""
local ERROR_MESG=""
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=""
local NUMBER_TYPE=""
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=""
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
回答by Menzies
Try:
尝试:
#!/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 "$ ./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
usage:" && grep " .)\ #" $ ./getopts.sh -R 123 -d /foo
Dir:
Level: 123
exit 0
;;
esac
done
echo Dir: $dir
echo Level: $level
I think the above code will work for your purposes while still using getopts
. I've added the following three lines to your code when getopts
encounters -R
:
我认为上面的代码在仍然使用getopts
. 当getopts
遇到以下三行时,我已将以下三行添加到您的代码中-R
:
If -R
is encountered and the first argument looks like another getopts parameter, level is set to the default value of 1
, and then the $OPTIND
variable is reduced by one. The next time getopts
goes to grab an argument, it will grab the correct argument instead of skipping it.
如果-R
遇到并且第一个参数看起来像另一个 getopts 参数,则级别设置为默认值1
,然后将$OPTIND
变量减一。下一次getopts
去抓取一个参数时,它会抓取正确的参数而不是跳过它。
Here is similar example based on the code from Jan Schampera's comment at this tutorial:
以下是基于本教程中 Jan Schampera 评论中的代码的类似示例:
##代码##When you discover that OPTARG von -c is something beginning with a hyphen, then reset OPTIND and re-run getopts (continue the while loop). Oh, of course, this isn't perfect and needs some more robustness. It's just an example.
当您发现 OPTARG von -c 是以连字符开头的内容时,请重置 OPTIND 并重新运行 getopts(继续 while 循环)。哦,当然,这并不完美,需要更多的鲁棒性。这只是一个例子。
回答by Menzies
The following code solves this problem by checking for a leading dash and if found decrements OPTIND to point back to the skipped option for processing. This generally works fine except that you do not know the order the user will place options on the command line - if your optional argument option is last and does not provide an argument getopts will want to error out.
下面的代码通过检查前导破折号来解决这个问题,如果找到则递减 OPTIND 以指向跳过的选项进行处理。这通常可以正常工作,除非您不知道用户将选项放在命令行上的顺序 - 如果您的可选参数选项是最后一个并且没有提供参数 getopts 将要出错。
To fix the problem of the final argument missing, the "$@" array simply has an empty string "$@ " appended so that getopts will be satisfied that it has gobbled up yet another option argument. To fix this new empty argument a variable is set that holds the total count of all options to be processed - when the last option is being processed a helper function called trim is called and removes the empty string prior to the value being utilized.
为了解决缺少最后一个参数的问题,“$@”数组只附加了一个空字符串“$@”,这样 getopts 就会满足它已经吞噬了另一个选项参数。为了修复这个新的空参数,设置了一个变量来保存要处理的所有选项的总数 - 当处理最后一个选项时,会调用一个名为 trim 的辅助函数,并在使用该值之前删除空字符串。
This is not working code, it has only place holders but you can easily modify it and with a little bit of care it can be useful to build a robust system.
这不是工作代码,它只有占位符,但您可以轻松修改它,只要稍加注意,它就会有助于构建一个健壮的系统。
##代码##回答by kenorb
You can always decide to differentiate the option with lowercase or uppercase.
您始终可以决定使用小写或大写来区分选项。
However my idea is to call getopts
twice and 1st time parse without arguments ignoring them (R
) then 2nd time parse only that option with argument support (R:
). The only trick is that OPTIND
(index) needs to be changed during processing, as it keeps pointer to the current argument.
然而,我的想法是调用getopts
两次,第一次解析没有参数忽略它们(R
),然后第二次仅解析带有参数支持的选项(R:
)。唯一的技巧是OPTIND
(index) 在处理期间需要更改,因为它保持指向当前参数的指针。
Here is the code:
这是代码:
##代码##Here are few tests for scenarios which works:
以下是一些适用于场景的测试:
##代码##Scenarios which doesn't work (so the code needs a bit of more tweaks):
不工作的场景(所以代码需要更多的调整):
##代码##More information about getopts
usage can be found in man bash
.
有关getopts
使用的更多信息可以在 中找到man bash
。
See also: Small getopts tutorialat Bash Hackers Wiki
另请参阅:Bash Hackers Wiki 上的Small getopts 教程