如何使用内置 Bash getopts 的长选项?
Posted
技术标签:
【中文标题】如何使用内置 Bash getopts 的长选项?【英文标题】:How can I use long options with the Bash getopts builtin? 【发布时间】:2012-08-14 21:27:07 【问题描述】:我正在尝试使用 Bash getopts 解析 -temp
选项。我这样调用我的脚本:
./myscript -temp /foo/bar/someFile
这是我用来解析选项的代码。
while getopts "temp:shots:o:" option; do
case $option in
temp) TMPDIR="$OPTARG" ;;
shots) NUMSHOTS="$OPTARG" ;;
o) OUTFILE="$OPTARG" ;;
*) usage ;;
esac
done
shift $(($OPTIND - 1))
[ $# -lt 1 ] && usage
【问题讨论】:
How do I parse command line arguments in Bash?的可能重复 我不同意它是重复的。这个问题是更笼统的如何解析,而这个问题更具体的是如何解析长选项。 【参考方案1】:您不能将 getopts Bash 内置函数用于长选项——至少,不必构建自己的解析函数。你应该看看 /usr/bin/getopt 二进制文件(在我的系统上由util-linux 包提供;你的里程可能会有所不同)。
请参阅 getopt(1) 了解特定的调用选项。
【讨论】:
我认为/usr/bin/getopt
也不支持长选项。【参考方案2】:
getopts
被 shell 程序用来解析只有 1 个字符的位置参数
(没有 GNU 风格的长选项(--myoption)或 XF86 风格的长选项(-myoption))
【讨论】:
【参考方案3】:getopts
只能解析短选项。
大多数系统也有一个外部的getopt
命令,但是 getopt 不是标准的,并且通常被设计破坏,因为它不能安全地处理所有参数(带有空格和空参数的参数),只有 GNU getopt 可以处理它们安全,但前提是您以特定于 GNU 的方式使用它。
更简单的选择是两者都不使用,只需使用 while 循环迭代脚本的参数并自己进行解析。
有关示例,请参阅 http://mywiki.wooledge.org/BashFAQ/035。
【讨论】:
这里不需要重新发明***,只需使用 getopts 并查找其他方法来解析长参数,或者像 @mcoolive 在这里展示的那样欺骗它【参考方案4】:确实 builtin bash getopts
只解析短选项,
但是您仍然可以添加几行脚本来使 getopts 处理长选项。
这是http://www.uxora.com/unix/shell-script/22-handle-long-options-with-getopts中的部分代码
#== set options ==#
SCRIPT_OPTS=':fbF:B:-:h'
typeset -A ARRAY_OPTS
ARRAY_OPTS=(
[foo]=f
[bar]=b
[foobar]=F
[barfoo]=B
[help]=h
[man]=h
)
#== parse options ==#
while getopts $SCRIPT_OPTS OPTION ; do
#== translate long options to short ==#
if [[ "x$OPTION" == "x-" ]]; then
LONG_OPTION=$OPTARG
LONG_OPTARG=$(echo $LONG_OPTION | grep "=" | cut -d'=' -f2)
LONG_OPTIND=-1
[[ "x$LONG_OPTARG" = "x" ]] && LONG_OPTIND=$OPTIND || LONG_OPTION=$(echo $OPTARG | cut -d'=' -f1)
[[ $LONG_OPTIND -ne -1 ]] && eval LONG_OPTARG="\$$LONG_OPTIND"
OPTION=$ARRAY_OPTS[$LONG_OPTION]
[[ "x$OPTION" = "x" ]] && OPTION="?" OPTARG="-$LONG_OPTION"
if [[ $( echo "$SCRIPT_OPTS" | grep -c "$OPTION:" ) -eq 1 ]]; then
if [[ "x$LONG_OPTARG" = "x" ]] || [[ "$LONG_OPTARG" = -* ]]; then
OPTION=":" OPTARG="-$LONG_OPTION"
else
OPTARG="$LONG_OPTARG";
if [[ $LONG_OPTIND -ne -1 ]]; then
[[ $OPTIND -le $Optnum ]] && OPTIND=$(( $OPTIND+1 ))
shift $OPTIND
OPTIND=1
fi
fi
fi
fi
#== options follow by another option instead of argument ==#
if [[ "x$OPTION" != "x:" ]] && [[ "x$OPTION" != "x?" ]] && [[ "$OPTARG" = -* ]]; then
OPTARG="$OPTION" OPTION=":"
fi
#== manage options ==#
case "$OPTION" in
f ) foo=1 bar=0 ;;
b ) foo=0 bar=1 ;;
B ) barfoo=$OPTARG ;;
F ) foobar=1 && foobar_name=$OPTARG ;;
h ) usagefull && exit 0 ;;
: ) echo "$SCRIPT_NAME: -$OPTARG: option requires an argument" >&2 && usage >&2 && exit 99 ;;
? ) echo "$SCRIPT_NAME: -$OPTARG: unknown option" >&2 && usage >&2 && exit 99 ;;
esac
done
shift $(($OPTIND - 1))
这是一个测试:
# Short options test
$ ./foobar_any_getopts.sh -bF "Hello world" -B 6 file1 file2
foo=0 bar=1
barfoo=6
foobar=1 foobar_name=Hello world
files=file1 file2
# Long and short options test
$ ./foobar_any_getopts.sh --bar -F Hello --barfoo 6 file1 file2
foo=0 bar=1
barfoo=6
foobar=1 foobar_name=Hello
files=file1 file2
否则在最近的 Korn Shell ksh93 中,getopts
可以自然地解析长选项,甚至显示类似的手册页。 (见http://www.uxora.com/unix/shell-script/20-getopts-with-man-page-and-long-options)
米歇尔·冯维莱。
【讨论】:
很好,谢谢。不幸的是,如果参数包含 '=' 或参数是空字符串,它会中断。我应该建议一个支持 '=' 和空字符串作为参数的版本吗?【参考方案5】:正如其他人解释的那样,getopts 不会解析长选项。您可以使用 getopt,但它不可移植(并且在某些平台上已损坏...)
作为一种解决方法,您可以实现一个 shell 循环。这是一个在使用标准 getopts 命令之前将长选项转换为短选项的示例(我认为它更简单):
# Transform long options to short ones
for arg in "$@"; do
shift
case "$arg" in
"--help") set -- "$@" "-h" ;;
"--rest") set -- "$@" "-r" ;;
"--ws") set -- "$@" "-w" ;;
*) set -- "$@" "$arg"
esac
done
# Default behavior
rest=false; ws=false
# Parse short options
OPTIND=1
while getopts "hrw" opt
do
case "$opt" in
"h") print_usage; exit 0 ;;
"r") rest=true ;;
"w") ws=true ;;
"?") print_usage >&2; exit 1 ;;
esac
done
shift $(expr $OPTIND - 1) # remove options from positional parameters
【讨论】:
这很好,只是如果传递了无效参数,它无法正确通知用户,而是声明illegal option -- -
。我在脚本中添加了以下内容以提供帮助:"--"*) usage $arg; exit 2;;
这可能是个好主意。我的示例可以以相同的方式进行改进,以允许使用“--separator=STRING”之类的语法。我选择实现一个简单的循环并且不允许所有可能的语法。它可以改进。
这是迄今为止我找到的最直接的解决方案。这允许您继续使用 getopts 并支持--
样式。不错!
作为一个魅力。感谢您分享您的解决方案。
请使用需要参数的长选项进行扩展。【参考方案6】:
虽然这个问题是在 2 年前发布的,但我发现自己也需要支持 XFree86 风格的长选项;而且我还想从 getopts 中获取我能得到的东西。考虑 GCC 开关 -rdynamic
。我将r
标记为标志字母,并期望dynamic
在$OPTARG
内...但是,我想拒绝-r dynamic
,同时接受r
之后的其他选项。
我在下面提出的想法建立在观察到$OPTIND
将比其他情况大一,如果空间(间隙)跟随标志。因此,我定义了一个 bash 变量来保存 $OPTIND
的先前值,称为 $PREVOPTIND
,并在 while
循环结束时对其进行更新。如果$OPTIND
是1
大于$PREVOPTIND
,我们没有差距(即-rdynamic
);并且$GAP
设置为false
。相反,如果 $OPTIND
是 2
大于 $PREVOPTIND
,我们确实有一个间隙(例如 -r dynamic
),并且 $GAP
设置为 true
。
usage() echo usage: error from $1; exit -1;
OPTIND=1
PREVOPTIND=$OPTIND
while getopts "t:s:o:" option; do
GAP=$((OPTIND-(PREVOPTIND+1)))
case $option in
t) case "$OPTARG" in
emp) # i.e. -temp
((GAP)) && usage "-$option and $OPTARG"
TMPDIR="$OPTARG"
;;
*)
true
;;
esac
;;
s) case "$OPTARG" in
hots) # i.e. -shots
((GAP)) && usage
NUMSHOTS="$OPTARG"
;;
*) usage "-$option and $OPTARG" ;;
esac
;;
o) OUTFILE="$OPTARG" ;;
*) usage "-$option and $OPTARG" ;;
esac
PREVOPTIND=$OPTIND
done
shift $(($OPTIND - 1))
【讨论】:
【参考方案7】:感谢@mcoolive。
我能够使用您的 $@ 想法将整个单词和长选项转换为单字母选项。想提醒任何使用这个想法的人,在通过 getopts 循环运行参数之前,我还必须包含 shift $(expr $OPTIND - 1)
。
目的完全不同,但效果很好。
# convert long word options to short word for ease of use and portability
for argu in "$@"; do
shift
#echo "curr arg = $1"
case "$argu" in
"-start"|"--start")
# param=param because no arg is required
set -- "$@" "-s"
;;
"-pb"|"--pb"|"-personalbrokers"|"--personalbrokers")
# pb +arg required
set -- "$@" "-p $1"; #echo "arg=$argu"
;;
"-stop"|"--stop")
# param=param because no arg is required
set -- "$@" "-S"
;;
# the catch all option here removes all - symbols from an
# argument. if an option is attempted to be passed that is
# invalid, getopts knows what to do...
*) [[ $(echo $argu | grep -E "^-") ]] && set -- "$@" "$argu//-/" || echo "no - symbol. not touching $argu" &>/dev/null
;;
esac
done
#echo -e "\n final option conversions = $@\n"
# remove options from positional parameters for getopts parsing
shift $(expr $OPTIND - 1)
declare -i runscript=0
# only p requires an argument hence the p:
while getopts "sSp:" param; do
[[ "$param" == "p" ]] && [[ $(echo $OPTARG | grep -E "^-") ]] && funcUsage "order"
#echo $param
#echo "OPTIND=$OPTIND"
case $param in
s)
OPTARG=$OPTARG/ /
getoptsRan=1
echo "$param was passed and this is it's arg $OPTARG"
arg0=start
;;
p)
OPTARG=$OPTARG/ /
getoptsRan=1
echo "$param was passed and this is it's arg $OPTARG"
[[ "$OPTARG" == "all" ]] && echo -e "argument \"$OPTARG\" accepted. continuing." && (( runscript += 1 )) || usage="open"
[[ $( echo $pbString | grep -w "$OPTARG" ) ]] && echo -e "pb $OPTARG was validated. continuing.\n" && (( runscript += 1 )) || usage="personal"
[[ "$runscript" -lt "1" ]] && funcUsage "$usage" "$OPTARG"
arg0=start
;;
S)
OPTARG=$OPTARG/ /
getoptsRan=1
echo "$param was passed and this is it's arg $OPTARG"
arg0=stop
;;
*)
getoptsRan=1
funcUsage
echo -e "Invalid argument\n"
;;
esac
done
funcBuildExcludes "$@"
shift $((OPTIND-1))
【讨论】:
【参考方案8】:如果你在使用内置getopts
时遇到问题,一个简单的DIY:
用途:
$ ./test-args.sh --a1 a1 --a2 "a 2" --a3 --a4= --a5=a5 --a6="a 6"
a1 = "a1"
a2 = "a 2"
a3 = "TRUE"
a4 = ""
a5 = "a5"
a6 = "a 6"
a7 = ""
脚本:
#!/bin/bash
function main()
ARGS=`getArgs "$@"`
a1=`echo "$ARGS" | getNamedArg a1`
a2=`echo "$ARGS" | getNamedArg a2`
a3=`echo "$ARGS" | getNamedArg a3`
a4=`echo "$ARGS" | getNamedArg a4`
a5=`echo "$ARGS" | getNamedArg a5`
a6=`echo "$ARGS" | getNamedArg a6`
a7=`echo "$ARGS" | getNamedArg a7`
echo "a1 = \"$a1\""
echo "a2 = \"$a2\""
echo "a3 = \"$a3\""
echo "a4 = \"$a4\""
echo "a5 = \"$a5\""
echo "a6 = \"$a6\""
echo "a7 = \"$a7\""
exit 0
function getArgs()
for arg in "$@"; do
echo "$arg"
done
function getNamedArg()
ARG_NAME=$1
sed --regexp-extended --quiet --expression="
s/^--$ARG_NAME=(.*)\$/\1/p # Get arguments in format '--arg=value': [s]ubstitute '--arg=value' by 'value', and [p]rint
/^--$ARG_NAME\$/ # Get arguments in format '--arg value' ou '--arg'
n # - [n]ext, because in this format, if value exists, it will be the next argument
/^--/! p # - If next doesn't starts with '--', it is the value of the actual argument
/^--/ # - If next do starts with '--', it is the next argument and the actual argument is a boolean one
# Then just repla[c]ed by TRUE
c TRUE
"
main "$@"
【讨论】:
【参考方案9】:实现这一点的最简单方法是借助 getopt 和 --longoptions
试试这个,希望有用
# Read command line options
ARGUMENT_LIST=(
"input1"
"input2"
"input3"
)
# read arguments
opts=$(getopt \
--longoptions "$(printf "%s:," "$ARGUMENT_LIST[@]")" \
--name "$(basename "$0")" \
--options "" \
-- "$@"
)
echo $opts
eval set --$opts
while true; do
case "$1" in
--input1)
shift
empId=$1
;;
--input2)
shift
fromDate=$1
;;
--input3)
shift
toDate=$1
;;
--)
shift
break
;;
esac
shift
done
这就是你调用shell脚本的方式
myscript.sh --input1 "ABC" --input2 "PQR" --input2 "XYZ"
【讨论】:
以上是关于如何使用内置 Bash getopts 的长选项?的主要内容,如果未能解决你的问题,请参考以下文章