我希望使用我的shell脚本调用多种形式的命令行选项.
我知道getopts
可以使用,但就像在Perl中一样,我无法对shell做同样的事情.
关于如何做到这一点的任何想法,以便我可以使用如下选项:
./shell.sh --copyfile abc.pl /tmp/ ./shell.sh -c abc.pl /tmp/
在上面,两个命令对我的shell意味着相同的东西,但使用getopts
,我还没有能够实现这些?
getopt
并且getopts
是不同的野兽,人们似乎对他们所做的事情有一些误解. getopts
是一个内置命令,bash
用于处理循环中的命令行选项,并将每个找到的选项和值依次分配给内置变量,以便您可以进一步处理它们. getopt
但是,它是一个外部实用程序,它实际上并不像 bash getopts
,Perl Getopt
模块或Python optparse
/ argparse
模块那样为您处理选项.所有这一切getopt
确实是规范化所传递的选项-即它们转换为更规范的形式,因此,它是一个shell脚本来处理它们更容易.例如,应用程序getopt
可能会转换以下内容:
myscript -ab infile.txt -ooutfile.txt
进入这个:
myscript -a -b -o outfile.txt infile.txt
你必须自己做实际的处理.getopt
如果对指定选项的方式进行各种限制,则根本不必使用:
每个参数只放一个选项;
所有选项都在任何位置参数之前(即非选项参数);
对于具有值的选项(例如,-o
上面),该值必须作为单独的参数(在空格之后).
为什么用getopt
而不是getopts
?基本原因是只有GNU getopt
才能支持长命名的命令行选项.1(GNU getopt
是Linux上的默认设置.Mac OS X和FreeBSD有一个基本的,非常有用getopt
,但可以安装GNU版本;见下文.)
例如,这是一个使用GNU的例子getopt
,从我的脚本调用javawrap
:
# NOTE: This requires GNU getopt. On Mac OS X and FreeBSD, you have to install this # separately; see below. TEMP=`getopt -o vdm: --long verbose,debug,memory:,debugfile:,minheap:,maxheap: \ -n 'javawrap' -- "$@"` if [ $? != 0 ] ; then echo "Terminating..." >&2 ; exit 1 ; fi # Note the quotes around `$TEMP': they are essential! eval set -- "$TEMP" VERBOSE=false DEBUG=false MEMORY= DEBUGFILE= JAVA_MISC_OPT= while true; do case "$1" in -v | --verbose ) VERBOSE=true; shift ;; -d | --debug ) DEBUG=true; shift ;; -m | --memory ) MEMORY="$2"; shift 2 ;; --debugfile ) DEBUGFILE="$2"; shift 2 ;; --minheap ) JAVA_MISC_OPT="$JAVA_MISC_OPT -XX:MinHeapFreeRatio=$2"; shift 2 ;; --maxheap ) JAVA_MISC_OPT="$JAVA_MISC_OPT -XX:MaxHeapFreeRatio=$2"; shift 2 ;; -- ) shift; break ;; * ) break ;; esac done
这允许您指定类似--verbose -dm4096 --minh=20 --maxhe 40 --debugfi="/Users/John Johnson/debug.txt"
或类似的选项.调用的效果getopt
是规范化选项,--verbose -d -m 4096 --minheap 20 --maxheap 40 --debugfile "/Users/John Johnson/debug.txt"
以便您可以更轻松地处理它们.引用"$1"
并且"$2"
很重要,因为它确保正确处理带有空格的参数.
如果删除前9行(通过该eval set
行的所有内容),代码仍然有效!但是,您的代码在它接受的各种选项中会更加挑剔:特别是,您必须以上述"规范"形式指定所有选项.getopt
但是,通过使用,您可以对单字母选项进行分组,使用较短的非模糊形式的长选项,使用--file foo.txt
或--file=foo.txt
样式,使用-m 4096
或-m4096
样式,混合选项和任何顺序的非选项等. getopt
如果找到无法识别或不明确的选项,也会输出错误消息.
注意:实际上有两个完全不同的版本getopt
,基本getopt
和GNU getopt
,具有不同的功能和不同的调用约定.2 Basic getopt
非常破碎:它不仅不处理长选项,它甚至不能处理参数或空参数内的嵌入空格,而是getopts
这样做.上面的代码在基本代码中不起作用getopt
.GNU getopt
默认安装在Linux上,但在Mac OS X和FreeBSD上需要单独安装.在Mac OS X上,安装MacPorts(http://www.macports.org),然后sudo port install getopt
安装GNU getopt
(通常是/opt/local/bin
),并确保它/opt/local/bin
在你的shell路径之前/usr/bin
.在FreeBSD上,安装misc/getopt
.
修改自己程序的示例代码的快速指南:在前几行中,除了调用的行之外,所有都是"样板文件"应该保持不变getopt
.您应该在之后更改程序名称,在之后-n
指定短选项,在之后指定-o
长选项--long
.在带有值的选项后放置冒号.
最后,如果您看到set
代替的代码eval set
,那么它就是为BSD编写的getopt
.您应该将其更改为使用eval set
样式,该样式适用于两个版本getopt
,而普通版本set
无法正常使用GNU getopt
.
1实际上,getopts
在ksh93
支持长命名的选项中,但是这个shell并不经常使用bash
.在zsh
,用于zparseopts
获得此功能.
2从技术上讲,"GNU getopt
"是用词不当; 这个版本实际上是为Linux而不是GNU项目编写的.但是,它遵循所有GNU约定,并且getopt
通常使用术语"GNU "(例如在FreeBSD上).
可以考虑三种实现:
Bash内置getopts
.这不支持带有双短划线前缀的长选项名称.它仅支持单字符选项.
BSD UNIX实现独立getopt
命令(这是MacOS使用的).这也不支持长选项.
独立的GNU实现getopt
.GNU getopt(3)
(getopt(1)
Linux上的命令行使用)支持解析长选项.
其他一些答案显示了使用bash内置getopts
模拟长选项的解决方案.该解决方案实际上是一个短的选项,其字符是" - ".所以你得到" - "作为旗帜.然后,随后的任何内容都将成为OPTARG,并使用嵌套测试OPTARG case
.
这很聪明,但它带有警告:
getopts
无法强制执行opt规范.如果用户提供无效选项,则无法返回错误.在解析OPTARG时,您必须自己进行错误检查.
OPTARG用于长选项名称,当您的长选项本身具有参数时,这会使用量变得复杂.您最终必须自己编写代码作为附加案例.
因此,尽管可以编写更多代码来解决缺乏对长选项的支持的问题,但这需要做更多的工作,并且部分地违背了使用getopt解析器来简化代码的目的.
Bash builtin getopts函数可用于通过在optspec中放入一个破折号字符后跟冒号来解析长选项:
#!/usr/bin/env bash optspec=":hv-:" while getopts "$optspec" optchar; do case "${optchar}" in -) case "${OPTARG}" in loglevel) val="${!OPTIND}"; OPTIND=$(( $OPTIND + 1 )) echo "Parsing option: '--${OPTARG}', value: '${val}'" >&2; ;; loglevel=*) val=${OPTARG#*=} opt=${OPTARG%=$val} echo "Parsing option: '--${opt}', value: '${val}'" >&2 ;; *) if [ "$OPTERR" = 1 ] && [ "${optspec:0:1}" != ":" ]; then echo "Unknown option --${OPTARG}" >&2 fi ;; esac;; h) echo "usage: $0 [-v] [--loglevel[=]]" >&2 exit 2 ;; v) echo "Parsing option: '-${optchar}'" >&2 ;; *) if [ "$OPTERR" != 1 ] || [ "${optspec:0:1}" = ":" ]; then echo "Non-option argument: '-${OPTARG}'" >&2 fi ;; esac done
复制到当前工作目录getopts_test.sh
中的可执行文件名= 后,可以生成类似的输出
$ ./getopts_test.sh $ ./getopts_test.sh -f Non-option argument: '-f' $ ./getopts_test.sh -h usage: code/getopts_test.sh [-v] [--loglevel[=]] $ ./getopts_test.sh --help $ ./getopts_test.sh -v Parsing option: '-v' $ ./getopts_test.sh --very-bad $ ./getopts_test.sh --loglevel Parsing option: '--loglevel', value: '' $ ./getopts_test.sh --loglevel 11 Parsing option: '--loglevel', value: '11' $ ./getopts_test.sh --loglevel=11 Parsing option: '--loglevel', value: '11'
显然,getopts既不OPTERR
对long选项执行检查也不执行option-argument解析.上面的脚本片段显示了如何手动完成此操作.基本原理也适用于Debian Almquist shell("dash").请注意特殊情况:
getopts -- "-:" ## without the option terminator "-- " bash complains about "-:" getopts "-:" ## this works in the Debian Almquist shell ("dash")
请注意,正如来自http://mywiki.wooledge.org/BashFAQ的 GreyCat所指出的,这个技巧利用了shell的非标准行为,允许使用option-argument(即"-f filename"中的文件名)连接到选项(如"-ffilename").该POSIX标准说必须有它们之间的空间,这在的情况下" - longoption"将终止选项的解析,并把所有longoptions成非选项参数.
内置getopts
命令仍为AFAIK,仅限于单字符选项.
有(或曾经是)一个外部程序getopt
,它会重新组织一组选项,以便更容易解析.您可以调整该设计以处理长选项.用法示例:
aflag=no bflag=no flist="" set -- $(getopt abf: "$@") while [ $# -gt 0 ] do case "$1" in (-a) aflag=yes;; (-b) bflag=yes;; (-f) flist="$flist $2"; shift;; (--) shift; break;; (-*) echo "$0: error - unrecognized option $1" 1>&2; exit 1;; (*) break;; esac shift done # Process remaining non-option arguments ...
您可以使用与getoptlong
命令类似的方案.
请注意,外部getopt
程序的根本弱点是难以处理带有空格的参数,并且难以准确地保留这些空间.这就是内置getopts
优越的原因,尽管它只处理单字母选项.
这是一个实际使用带有长选项的getopt的示例:
aflag=no bflag=no cargument=none # options may be followed by one colon to indicate they have a required argument if ! options=$(getopt -o abc: -l along,blong,clong: -- "$@") then # something went wrong, getopt will put out an error message for us exit 1 fi set -- $options while [ $# -gt 0 ] do case $1 in -a|--along) aflag="yes" ;; -b|--blong) bflag="yes" ;; # for options with required arguments, an additional shift is required -c|--clong) cargument="$2" ; shift;; (--) shift; break;; (-*) echo "$0: error - unrecognized option $1" 1>&2; exit 1;; (*) break;; esac shift done
可以通过标准getopts
内置解析长选项作为-
"选项"的"参数"
这是可移植的本机POSIX shell - 不需要外部程序或基本原理.
本指南执行长选项作为参数传递给-
选项,因此--alpha
被看作getopts
是-
与参数alpha
和--bravo=foo
被视为-
与参数bravo=foo
.真正的论点可以用简单的替代品来收获:${OPTARG#*=}
.
在此示例中,-b
(及其长形式--bravo
)具有强制选项(请注意对长格式强制执行的手动重建).长参数的非布尔选项在等号之后出现,例如--bravo=foo
(长选项的空格分隔符很难实现).
因为这样使用getopts
,这个解决方案支持使用类似cmd -ac --bravo=foo -d FILE
(它具有组合选项-a
和 - c
并将长选项与标准选项交错),而大多数其他答案在这里要么努力要么不能做到.
while getopts ab:c-: arg; do
case $arg in
a ) ARG_A=true ;;
b ) ARG_B="$OPTARG" ;;
c ) ARG_C=true ;;
- ) LONG_OPTARG="${OPTARG#*=}"
case $OPTARG in
alpha ) ARG_A=true ;;
bravo=?* ) ARG_B="$LONG_OPTARG" ;;
bravo* ) echo "No arg for --$OPTARG option" >&2; exit 2 ;;
charlie ) ARG_C=true ;;
alpha* | charlie* )
echo "No arg allowed for --$OPTARG option" >&2; exit 2 ;;
'' ) break ;; # "--" terminates argument processing
* ) echo "Illegal option --$OPTARG" >&2; exit 2 ;;
esac ;;
\? ) exit 2 ;; # getopts already reported the illegal option
esac
done
shift $((OPTIND-1)) # remove parsed options and args from $@ list
当参数是一个破折号(-
)时,它还有两个组件:标志名称和(可选)其参数.我将这些标准方式划分为任何命令,第一个等号(=
). $LONG_OPTARG
因此,仅仅是$OPTARG
没有国旗名称或等号的内容.
内部case
手动实现长选项,因此需要一些内务处理:
bravo=?
匹配--bravo=foo
但不匹配--bravo=
(注意:case
第一场比赛后停止)
bravo*
如下,并指出在缺少必需的参数--bravo
和--bravo=
alpha* | charlie*
捕获给不支持它们的选项的参数
''
用于支持碰巧以破折号开头的非选项
*
捕获所有其他长选项并重新创建getopts为无效选项抛出的错误
您不一定需要所有这些家政用品.例如,您可能希望--bravo
拥有一个可选参数(-b
由于其中的限制而无法支持getopts
).只需删除=?
相关的故障情况,然后${ARG_B:=$DEFAULT_ARG_B}
在第一次使用时拨打电话$ARG_B
.
看看shFlags是一个可移植的shell库(意思是:Linux,Solaris等上的sh,bash,dash,ksh,zsh).
它使添加新标志就像在脚本中添加一行一样简单,它提供了一个自动生成的使用功能.
这是一个简单的Hello, world!
使用shFlag:
#!/bin/sh # source shflags from current directory . ./shflags # define a 'name' command-line string flag DEFINE_string 'name' 'world' 'name to say hello to' 'n' # parse the command-line FLAGS "$@" || exit 1 eval set -- "${FLAGS_ARGV}" # say hello echo "Hello, ${FLAGS_name}!"
对于具有支持长选项的增强型getopt(例如Linux)的操作系统,您可以执行以下操作:
$ ./hello_world.sh --name Kate Hello, Kate!
其余的,你必须使用短选项:
$ ./hello_world.sh -n Kate Hello, Kate!
添加新标志就像添加新标志一样简单DEFINE_ call
.
getopts
短/长选项和参数
foobar -f --bar
foobar --foo -b
foobar -bf --bar --foobar
foobar -fbFBAshorty --bar -FB --arguments = longhorn
foobar -fA"text shorty"-B --arguments ="text longhorn"
bash foobar -F --barfoo
sh foobar -B --foobar - ...
bash ./foobar -F --bar
Options=$@ Optnum=$# sfoo='no ' sbar='no ' sfoobar='no ' sbarfoo='no ' sarguments='no ' sARG=empty lfoo='no ' lbar='no ' lfoobar='no ' lbarfoo='no ' larguments='no ' lARG=empty
function _usage() { ###### U S A G E : Help and ERROR ###### cat <Options: -b --bar Set bar to yes ($foo) -f --foo Set foo to yes ($bart) -h --help Show this message -A --arguments=... Set arguments to yes ($arguments) AND get ARGUMENT ($ARG) -B --barfoo Set barfoo to yes ($barfoo) -F --foobar Set foobar to yes ($foobar) EOF } [ $# = 0 ] && _usage " >>>>>>>> no options given "
getops
有长/短标志以及长参数while getopts ':bfh-A:BF' OPTION ; do case "$OPTION" in b ) sbar=yes ;; f ) sfoo=yes ;; h ) _usage ;; A ) sarguments=yes;sARG="$OPTARG" ;; B ) sbarfoo=yes ;; F ) sfoobar=yes ;; - ) [ $OPTIND -ge 1 ] && optind=$(expr $OPTIND - 1 ) || optind=$OPTIND eval OPTION="\$$optind" OPTARG=$(echo $OPTION | cut -d'=' -f2) OPTION=$(echo $OPTION | cut -d'=' -f1) case $OPTION in --foo ) lfoo=yes ;; --bar ) lbar=yes ;; --foobar ) lfoobar=yes ;; --barfoo ) lbarfoo=yes ;; --help ) _usage ;; --arguments ) larguments=yes;lARG="$OPTARG" ;; * ) _usage " Long: >>>>>>>> invalid options (long) " ;; esac OPTIND=1 shift ;; ? ) _usage "Short: >>>>>>>> invalid options (short) " ;; esac done
################################################################## echo "----------------------------------------------------------" echo "RESULT short-foo : $sfoo long-foo : $lfoo" echo "RESULT short-bar : $sbar long-bar : $lbar" echo "RESULT short-foobar : $sfoobar long-foobar : $lfoobar" echo "RESULT short-barfoo : $sbarfoo long-barfoo : $lbarfoo" echo "RESULT short-arguments: $sarguments with Argument = \"$sARG\" long-arguments: $larguments and $lARG"
#!/bin/bash # foobar: getopts with short and long options AND arguments function _cleanup () { unset -f _usage _cleanup ; return 0 } ## Clear out nested functions on exit trap _cleanup INT EXIT RETURN ###### some declarations for this example ###### Options=$@ Optnum=$# sfoo='no ' sbar='no ' sfoobar='no ' sbarfoo='no ' sarguments='no ' sARG=empty lfoo='no ' lbar='no ' lfoobar='no ' lbarfoo='no ' larguments='no ' lARG=empty function _usage() { ###### U S A G E : Help and ERROR ###### cat <Options: -b --bar Set bar to yes ($foo) -f --foo Set foo to yes ($bart) -h --help Show this message -A --arguments=... Set arguments to yes ($arguments) AND get ARGUMENT ($ARG) -B --barfoo Set barfoo to yes ($barfoo) -F --foobar Set foobar to yes ($foobar) EOF } [ $# = 0 ] && _usage " >>>>>>>> no options given " ################################################################## ####### "getopts" with: short options AND long options ####### ####### AND short/long arguments ####### while getopts ':bfh-A:BF' OPTION ; do case "$OPTION" in b ) sbar=yes ;; f ) sfoo=yes ;; h ) _usage ;; A ) sarguments=yes;sARG="$OPTARG" ;; B ) sbarfoo=yes ;; F ) sfoobar=yes ;; - ) [ $OPTIND -ge 1 ] && optind=$(expr $OPTIND - 1 ) || optind=$OPTIND eval OPTION="\$$optind" OPTARG=$(echo $OPTION | cut -d'=' -f2) OPTION=$(echo $OPTION | cut -d'=' -f1) case $OPTION in --foo ) lfoo=yes ;; --bar ) lbar=yes ;; --foobar ) lfoobar=yes ;; --barfoo ) lbarfoo=yes ;; --help ) _usage ;; --arguments ) larguments=yes;lARG="$OPTARG" ;; * ) _usage " Long: >>>>>>>> invalid options (long) " ;; esac OPTIND=1 shift ;; ? ) _usage "Short: >>>>>>>> invalid options (short) " ;; esac done
其他方式...
# translate long options to short for arg do delim="" case "$arg" in --help) args="${args}-h ";; --verbose) args="${args}-v ";; --config) args="${args}-c ";; # pass through anything else *) [[ "${arg:0:1}" == "-" ]] || delim="\"" args="${args}${delim}${arg}${delim} ";; esac done # reset the translated args eval set -- $args # now we can process with getopt while getopts ":hvc:" opt; do case $opt in h) usage ;; v) VERBOSE=true ;; c) source $OPTARG ;; \?) usage ;; :) echo "option -$OPTARG requires an argument" usage ;; esac done
我有点解决这个问题:
# A string with command options options=$@ # An array with all the arguments arguments=($options) # Loop index index=0 for argument in $options do # Incrementing index index=`expr $index + 1` # The conditions case $argument in -a) echo "key $argument value ${arguments[index]}" ;; -abc) echo "key $argument value ${arguments[index]}" ;; esac done exit;
我愚蠢还是什么?getopt
而且getopts
很混乱.
如果您不想要getopt
依赖项,可以这样做:
while test $# -gt 0 do case $1 in # Normal option processing -h | --help) # usage and help ;; -v | --version) # version info ;; # ... # Special cases --) break ;; --*) # error unknown (long) option $1 ;; -?) # error unknown (short) option $1 ;; # FUN STUFF HERE: # Split apart combined short options -*) split=$1 shift set -- $(echo "$split" | cut -c 2- | sed 's/./-& /g') "$@" continue ;; # Done with options *) break ;; esac # for testing purposes: echo "$1" shift done
当然,那么你不能在一个短划线上使用长样式选项.如果你想添加缩短的版本(例如--verbos而不是--verbose),那么你需要手动添加它们.
但是,如果您希望获得getopts
功能以及长选项,这是一种简单的方法.
我还把这个片段放在一个要点上.
内置getopts
无法做到这一点.有一个外部的getopt(1)程序可以做到这一点,但你只能在Linux上从util-linux包中获得它.它附带一个示例脚本getopt-parse.bash.
还有一个getopts_long
写为shell函数.
#!/bin/bash while getopts "abc:d:" flag do case $flag in a) echo "[getopts:$OPTIND]==> -$flag";; b) echo "[getopts:$OPTIND]==> -$flag";; c) echo "[getopts:$OPTIND]==> -$flag $OPTARG";; d) echo "[getopts:$OPTIND]==> -$flag $OPTARG";; esac done shift $((OPTIND-1)) echo "[otheropts]==> $@" exit
.
#!/bin/bash until [ -z "$1" ]; do case $1 in "--dlong") shift if [ "${1:1:0}" != "-" ] then echo "==> dlong $1" shift fi;; *) echo "==> other $1"; shift;; esac done exit
在ksh93
,getopts
支持长名称...
while getopts "f(file):s(server):" flag do echo "$flag" $OPTIND $OPTARG done
或者我发现的教程已经说过了.试试看吧.
发明轮子的另一个版本......
这个函数是(希望)POSIX兼容的普通bourne shell替代GNU getopt.它支持短/长选项,可以接受强制/可选/无参数,并且指定选项的方式几乎与GNU getopt相同,因此转换是微不足道的.
当然,这仍然是放入脚本的相当大的代码块,但它大约是众所周知的getopt_long shell函数的一半,并且在您只想替换现有GNU getopt用途的情况下可能更好.
这是相当新的代码,所以YMMV(绝对请让我知道如果这实际上不是以任何理由POSIX兼容 - 便携性从一开始的打算,但我没有一个有用的POSIX测试环境).
代码和示例用法如下:
#!/bin/sh
# posix_getopt shell function
# Author: Phil S.
# Version: 1.0
# Created: 2016-07-05
# URL: http://stackoverflow.com/a/37087374/324105
# POSIX-compatible argument quoting and parameter save/restore
# http://www.etalabs.net/sh_tricks.html
# Usage:
# parameters=$(save "$@") # save the original parameters.
# eval "set -- ${parameters}" # restore the saved parameters.
save () {
local param
for param; do
printf %s\\n "$param" \
| sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/"
done
printf %s\\n " "
}
# Exit with status $1 after displaying error message $2.
exiterr () {
printf %s\\n "$2" >&2
exit $1
}
# POSIX-compatible command line option parsing.
# This function supports long options and optional arguments, and is
# a (largely-compatible) drop-in replacement for GNU getopt.
#
# Instead of:
# opts=$(getopt -o "$shortopts" -l "$longopts" -- "$@")
# eval set -- ${opts}
#
# We instead use:
# opts=$(posix_getopt "$shortopts" "$longopts" "$@")
# eval "set -- ${opts}"
posix_getopt () { # args: "$shortopts" "$longopts" "$@"
local shortopts longopts \
arg argtype getopt nonopt opt optchar optword suffix
shortopts="$1"
longopts="$2"
shift 2
getopt=
nonopt=
while [ $# -gt 0 ]; do
opt=
arg=
argtype=
case "$1" in
# '--' means don't parse the remaining options
( -- ) {
getopt="${getopt}$(save "$@")"
shift $#
break
};;
# process short option
( -[!-]* ) { # -x[foo]
suffix=${1#-?} # foo
opt=${1%$suffix} # -x
optchar=${opt#-} # x
case "${shortopts}" in
( *${optchar}::* ) { # optional argument
argtype=optional
arg="${suffix}"
shift
};;
( *${optchar}:* ) { # required argument
argtype=required
if [ -n "${suffix}" ]; then
arg="${suffix}"
shift
else
case "$2" in
( -* ) exiterr 1 "$1 requires an argument";;
( ?* ) arg="$2"; shift 2;;
( * ) exiterr 1 "$1 requires an argument";;
esac
fi
};;
( *${optchar}* ) { # no argument
argtype=none
arg=
shift
# Handle multiple no-argument parameters combined as
# -xyz instead of -x -y -z. If we have just shifted
# parameter -xyz, we now replace it with -yz (which
# will be processed in the next iteration).
if [ -n "${suffix}" ]; then
eval "set -- $(save "-${suffix}")$(save "$@")"
fi
};;
( * ) exiterr 1 "Unknown option $1";;
esac
};;
# process long option
( --?* ) { # --xarg[=foo]
suffix=${1#*=} # foo (unless there was no =)
if [ "${suffix}" = "$1" ]; then
suffix=
fi
opt=${1%=$suffix} # --xarg
optword=${opt#--} # xarg
case ",${longopts}," in
( *,${optword}::,* ) { # optional argument
argtype=optional
arg="${suffix}"
shift
};;
( *,${optword}:,* ) { # required argument
argtype=required
if [ -n "${suffix}" ]; then
arg="${suffix}"
shift
else
case "$2" in
( -* ) exiterr 1 \
"--${optword} requires an argument";;
( ?* ) arg="$2"; shift 2;;
( * ) exiterr 1 \
"--${optword} requires an argument";;
esac
fi
};;
( *,${optword},* ) { # no argument
if [ -n "${suffix}" ]; then
exiterr 1 "--${optword} does not take an argument"
fi
argtype=none
arg=
shift
};;
( * ) exiterr 1 "Unknown option $1";;
esac
};;
# any other parameters starting with -
( -* ) exiterr 1 "Unknown option $1";;
# remember non-option parameters
( * ) nonopt="${nonopt}$(save "$1")"; shift;;
esac
if [ -n "${opt}" ]; then
getopt="${getopt}$(save "$opt")"
case "${argtype}" in
( optional|required ) {
getopt="${getopt}$(save "$arg")"
};;
esac
fi
done
# Generate function output, suitable for:
# eval "set -- $(posix_getopt ...)"
printf %s "${getopt}"
if [ -n "${nonopt}" ]; then
printf %s "$(save "--")${nonopt}"
fi
}
用法示例:
# Process command line options
shortopts="hvd:c::s::L:D"
longopts="help,version,directory:,client::,server::,load:,delete"
#opts=$(getopt -o "$shortopts" -l "$longopts" -n "$(basename $0)" -- "$@")
opts=$(posix_getopt "$shortopts" "$longopts" "$@")
if [ $? -eq 0 ]; then
#eval set -- ${opts}
eval "set -- ${opts}"
while [ $# -gt 0 ]; do
case "$1" in
( -- ) shift; break;;
( -h|--help ) help=1; shift; break;;
( -v|--version ) version_help=1; shift; break;;
( -d|--directory ) dir=$2; shift 2;;
( -c|--client ) useclient=1; client=$2; shift 2;;
( -s|--server ) startserver=1; server_name=$2; shift 2;;
( -L|--load ) load=$2; shift 2;;
( -D|--delete ) delete=1; shift;;
esac
done
else
shorthelp=1 # getopt returned (and reported) an error.
fi
我不时只写shell脚本,但并没有实践,因此,感谢您提供任何反馈。
使用@Arvid Requate提出的策略,我们注意到了一些用户错误。忘记包含值的用户将不小心将下一个选项的名称视为值:
./getopts_test.sh --loglevel= --toc=TRUE
将导致“ loglevel”的值被视为“ --toc = TRUE”。这是可以避免的。
我从http://mwiki.wooledge.org/BashFAQ/035关于手动解析的讨论中采纳了一些有关检查CLI的用户错误的想法 。我将错误检查插入到处理“-”和“-”参数中。
然后,我开始摆弄语法,所以这里的任何错误都是我的错,而不是原始作者。
我的方法可以帮助希望长时间输入带有或不带有等号的用户。也就是说,它对“ --loglevel 9”的响应应该与“ --loglevel = 9”相同。在-/ space方法中,无法确定用户是否忘记了参数,因此需要进行一些猜测。
如果用户使用长/等号格式(--opt =),则=后面的空格会触发错误,因为未提供参数。
如果用户具有长/空格参数(--opt),则该脚本将导致失败,如果没有参数跟随(命令末尾)或参数以破折号开头)
如果您刚开始,“-opt = value”和“ --opt value”格式之间会有一个有趣的区别。带有等号的命令行参数被视为“ opt = value”,而要处理的工作是字符串解析,以“ =”分隔。相反,使用“ --opt值”时,参数的名称为“ opt”,因此我们面临着在命令行中获取下一个值的挑战。这就是@Arvid Requate使用$ {!OPTIND}(间接引用)的地方。我还是完全不了解,BashFAQ中的评论似乎警告了这种风格(http://mywiki.wooledge.org/BashFAQ/006)。顺便说一句,我不认为以前的海报有关OPTIND = $((($ OPTIND + 1))的重要性的评论是正确的。我是说
在此脚本的最新版本中,标志-v表示VERBOSE打印输出。
将其保存在名为“ cli-5.sh”的文件中,使其成为可执行文件,这些文件中的任何一个都将起作用,或者以所需的方式失败
./cli-5.sh -v --loglevel=44 --toc TRUE ./cli-5.sh -v --loglevel=44 --toc=TRUE ./cli-5.sh --loglevel 7 ./cli-5.sh --loglevel=8 ./cli-5.sh -l9 ./cli-5.sh --toc FALSE --loglevel=77 ./cli-5.sh --toc=FALSE --loglevel=77 ./cli-5.sh -l99 -t yyy ./cli-5.sh -l 99 -t yyy
这是对intpu用户进行错误检查的示例输出
$ ./cli-5.sh --toc --loglevel=77 ERROR: toc value must not have dash at beginning $ ./cli-5.sh --toc= --loglevel=77 ERROR: value for toc undefined
您应该考虑打开-v,因为它会打印出OPTIND和OPTARG的内部信息
#/usr/bin/env bash ## Paul Johnson ## 20171016 ## ## Combines ideas from ## /sf/ask/17360801/ ## by @Arvid Requate, and http://mwiki.wooledge.org/BashFAQ/035 # What I don't understand yet: # In @Arvid REquate's answer, we have # val="${!OPTIND}"; OPTIND=$(( $OPTIND + 1 )) # this works, but I don't understand it! die() { printf '%s\n' "$1" >&2 exit 1 } printparse(){ if [ ${VERBOSE} -gt 0 ]; then printf 'Parse: %s%s%s\n' "$1" "$2" "$3" >&2; fi } showme(){ if [ ${VERBOSE} -gt 0 ]; then printf 'VERBOSE: %s\n' "$1" >&2; fi } VERBOSE=0 loglevel=0 toc="TRUE" optspec=":vhl:t:-:" while getopts "$optspec" OPTCHAR; do showme "OPTARG: ${OPTARG[*]}" showme "OPTIND: ${OPTIND[*]}" case "${OPTCHAR}" in -) case "${OPTARG}" in loglevel) #argument has no equal sign opt=${OPTARG} val="${!OPTIND}" ## check value. If negative, assume user forgot value showme "OPTIND is {$OPTIND} {!OPTIND} has value \"${!OPTIND}\"" if [[ "$val" == -* ]]; then die "ERROR: $opt value must not have dash at beginning" fi ## OPTIND=$(( $OPTIND + 1 )) # CAUTION! no effect? printparse "--${OPTARG}" " " "${val}" loglevel="${val}" shift ;; loglevel=*) #argument has equal sign opt=${OPTARG%=*} val=${OPTARG#*=} if [ "${OPTARG#*=}" ]; then printparse "--${opt}" "=" "${val}" loglevel="${val}" ## shift CAUTION don't shift this, fails othewise else die "ERROR: $opt value must be supplied" fi ;; toc) #argument has no equal sign opt=${OPTARG} val="${!OPTIND}" ## check value. If negative, assume user forgot value showme "OPTIND is {$OPTIND} {!OPTIND} has value \"${!OPTIND}\"" if [[ "$val" == -* ]]; then die "ERROR: $opt value must not have dash at beginning" fi ## OPTIND=$(( $OPTIND + 1 )) #?? printparse "--${opt}" " " "${val}" toc="${val}" shift ;; toc=*) #argument has equal sign opt=${OPTARG%=*} val=${OPTARG#*=} if [ "${OPTARG#*=}" ]; then toc=${val} printparse "--$opt" " -> " "$toc" ##shift ## NO! dont shift this else die "ERROR: value for $opt undefined" fi ;; help) echo "usage: $0 [-v] [--loglevel[=]] [--toc[=] ]" >&2 exit 2 ;; *) if [ "$OPTERR" = 1 ] && [ "${optspec:0:1}" != ":" ]; then echo "Unknown option --${OPTARG}" >&2 fi ;; esac;; h|-\?|--help) ## must rewrite this for all of the arguments echo "usage: $0 [-v] [--loglevel[=] ] [--toc[=] ]" >&2 exit 2 ;; l) loglevel=${OPTARG} printparse "-l" " " "${loglevel}" ;; t) toc=${OPTARG} ;; v) VERBOSE=1 ;; *) if [ "$OPTERR" != 1 ] || [ "${optspec:0:1}" = ":" ]; then echo "Non-option argument: '-${OPTARG}'" >&2 fi ;; esac done echo " After Parsing values " echo "loglevel $loglevel" echo "toc $toc"