Using getopts to process long and short command line options
我希望使用shell脚本调用长和短形式的命令行选项。
我知道可以使用
关于如何实现这一点的任何想法,以便我可以使用以下选项:
1 2 | ./shell.sh --copyfile abc.pl /tmp/ ./shell.sh -c abc.pl /tmp/ |
在上面,这两个命令对我的shell来说都是相同的,但是使用
1 | myscript -ab infile.txt -ooutfile.txt |
进入这个:好的。
1 | myscript -a -b -o outfile.txt infile.txt |
你必须自己做实际的处理。如果您对指定选项的方式进行了各种限制,则完全不必使用
- 每个参数只能有一个选项;
- 所有选项都位于任何位置参数(即非选项参数)之前;
- 对于具有值的选项(例如上面的
-o ),该值必须作为单独的参数(在空格之后)。
为什么用
例如,这里有一个使用gnu
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | # 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 |
这允许您指定诸如
如果删除前9行(通过
注:实际上有两个完全不同的版本的
为您自己的程序修改示例代码的快速指南:在前几行中,所有的都是"样板文件",应该保持不变,除了调用
最后,如果您看到的代码只是
1实际上,
2技术上,"gnu
可以考虑三种实现:
bash builtin
getopts 。这不支持带双破折号前缀的长选项名。它只支持单字符选项。bsd unix独立
getopt 命令的实现(这是MacOS使用的)。这也不支持长选项。独立
getopt 的GNU实现。gnugetopt(3) (由Linux上的命令行getopt(1) 使用)支持解析长选项。
其他一些答案显示了使用bash内置的
这很聪明,但也有一些警告:
getopts 不能执行opt规范,如果用户提供的选项无效,则不能返回错误。在分析optarg时,必须进行自己的错误检查。- optarg用于long选项名,当long选项本身有参数时,这会使用法复杂化。最后你不得不自己编写代码作为一个附加的案例。
因此,尽管可以编写更多的代码来解决对长选项缺乏支持的问题,但这要花费更多的工作,并且部分地破坏了使用getopt解析器简化代码的目的。
bash builtin getopts函数可用于解析长选项,方法是在optspec中输入破折号,后跟冒号:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | #!/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[=]<value>]">&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 |
复制到当前工作目录中的可执行文件name=
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | $ ./getopts_test.sh $ ./getopts_test.sh -f Non-option argument: '-f' $ ./getopts_test.sh -h usage: code/getopts_test.sh [-v] [--loglevel[=]<value>] $ ./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既不为长选项执行
1 2 | getopts --"-:" ## without the option terminator"--" bash complains about"-:" getopts"-:" ## this works in the Debian Almquist shell ("dash") |
注意,正如greycat在http://mywiki.wooledge.org/bashfaq上指出的那样,这个技巧利用了shell的非标准行为,它允许将选项参数(即"-f文件名"中的文件名)连接到选项(如"-f filename")。POSIX标准指出它们之间必须有一个空格,在使用"-longoption"的情况下,它将终止选项解析,并将所有longoption转换为非选项参数。
内置的
有(或曾经有)一个外部程序
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | 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 ... |
您可以对
注意,外部
下面是一个实际使用getopt的长选项示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | 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 |
长选项可由标准
这是可移植的本地posix shell——不需要外部程序或bashims。
本指南将长期权作为
在本例中,
因为它使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | 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 |
当参数是破折号(
内部
bravo=? 与--bravo=foo 匹配,但不与--bravo= 匹配(注:case 在第一次匹配后停止)bravo* 如下,注意到--bravo 和--bravo= 中缺少必要的论据。alpha* | charlie* 捕捉到不支持它们的选项的论据。'' 用于支持恰好以破折号开头的非期权。* 捕获所有其他长选项,并重新创建getopts为无效选项引发的错误。
你不一定需要所有这些家政用品。例如,您可能希望
要接受带有空格分隔参数的长选项,您需要一个(相当安全的)
1 2 3 4 5 6 7 | bravo=?* ) ARG_B="$LONG_OPTARG" ;; bravo ) eval"ARG_B="\$$OPTIND"" if [ -z"$ARG_B" ]; then echo"No arg for --$OPTARG option">&2; exit 2 fi OPTIND=$((OPTIND+1)) ;; bravo* ) echo"No arg for --$OPTARG option">&2; exit 2 ;; |
此附加条款直接添加在
保证一个非空值是非常重要的,并且需要实际检查它,因此在这种情况下,我们有一个条件并生成一个致命的错误。如果您允许它为空,则需要限制
本附加条款的最后一部分增加了
看看shflags,它是一个可移植的shell库(意思是:sh、bash、dash、ksh、zsh在Linux、solaris等上)。
它使添加新标志变得和向脚本中添加一行一样简单,并提供了一个自动生成的使用函数。
下面是一个使用shflag的简单
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | #!/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}!" |
对于具有支持长选项(例如Linux)的增强getopt的操作系统,可以执行以下操作:
1 2 | $ ./hello_world.sh --name Kate Hello, Kate! |
对于其余部分,必须使用short选项:
1 2 | $ ./hello_world.sh -n Kate Hello, Kate! |
添加新标志与添加新的
使用带有短/长选项和参数的
- FoBar -F-棒
- FoBOA-氧基-B
- foobar-bf--巴--foobar
- foobar-fbbashorty--bar-fb--arguments=longhorn
- foobar-fa"text-short"-b--arguments="text longhorn"
- 巴什福巴尔-F——巴什福罗
- sh foobar-b--foobar-…
- bash./foobar-f--巴
本例的一些声明
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | 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 |
使用函数的外观
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | function _usage() { ###### U S A G E : Help and ERROR ###### cat <<EOF foobar $Options $* Usage: foobar <[options]> 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" |
带长/短标志和长参数的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | 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 |
产量
1 2 3 4 5 6 7 | ################################################################## 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" |
将上述内容组合成一个内聚的脚本
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 | #!/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 <<EOF foobar $Options $* Usage: foobar <[options]> 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 |
另一种方式…
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | # 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 |
我是这样解决的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | # 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; |
我是哑巴还是什么?
如果您不希望
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 | 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而不是--verbos e),则需要手动添加这些版本。
但是,如果您希望获得
我也把这段话放在要点中。
内置的
还有一个作为shell函数编写的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | #!/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 |
.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | #!/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 |
在
1 2 3 4 | while getopts"f(file):s(server):" flag do echo"$flag" $OPTIND $OPTARG done |
我找到的教程中也有这样的说法。试试看。
发明了另一种轮子…
这个函数是一个(希望)posix兼容的普通bourne shell替换gnu getopt。它支持可以接受强制/可选/无参数的短/长选项,并且指定选项的方式几乎与GNUgetopt相同,因此转换非常简单。
当然,这仍然是可以放到脚本中的相当大的代码块,但是它大约是著名的getopt-long shell函数的一半,并且在您只想替换现有的gnu-getopt使用的情况下可能更可取。
这是一个非常新的代码,所以YMMV(当然,请告诉我这是否因为任何原因而与POSIX不兼容——可移植性从一开始就是我的意图,但我没有一个有用的POSIX测试环境)。
代码和示例用法如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 | #!/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\ "$param" \ | sed"s/'/'\\\''/g;1s/^/'/;\$s/\$/' \\\\/" done printf %s\ "" } # Exit with status $1 after displaying error message $2. exiterr () { printf %s\ "$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 } |
示例用法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | # 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提出的策略,我们注意到一些用户错误。忘记包含值的用户会意外地将下一个选项的名称视为值:
1 | ./getopts_test.sh --loglevel= --toc=TRUE |
将导致"loglevel"的值被视为"-toc=true"。这个罐头要避免。
我从http://mwiki.wooledge.org/bashfaq/035关于手动解析的讨论中修改了一些有关检查cli用户错误的想法。我在处理"-"和"-"参数时插入了错误检查。
然后我开始摆弄语法,所以这里的任何错误都是我的错,而不是最初的作者。
我的方法可以帮助那些喜欢使用或不使用等号输入long的用户。也就是说,它对"-loglevel 9"的响应应该与"-loglevel=9"的响应相同。在--/space方法中,无法确定用户是否忘记了参数,因此需要进行一些猜测。
如果您是从这个开始的,那么"-opt=value"和"-opt-value"格式之间有一个有趣的区别。使用等号,命令行参数被视为"opt=value",要处理的工作是字符串解析,在"="处分隔。相反,使用"-opt-value",参数的名称是"opt",我们面临获取命令行中提供的下一个值的挑战。这就是@arvid需要使用的地方$!optind,间接参考。我仍然不明白这一点,好吧,而且bashfaq中的评论似乎对这种风格发出了警告(http://mywiki.wooledge.org/bashfaq/006)。顺便说一句,我不认为之前海报上关于optind的重要性的评论是正确的($optind+1))。我的意思是说,我看不出省略它有什么坏处。
在这个脚本的最新版本中,flag-v意味着详细的打印输出。
将其保存在一个名为"cli-5.sh"的文件中,使其成为可执行文件,其中任何一个都可以工作,或者以所需的方式失败。
1 2 3 4 5 6 7 8 9 10 11 | ./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进行错误检查的示例输出
1 2 3 4 | $ ./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的内部。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 | #/usr/bin/env bash ## Paul Johnson ## 20171016 ## ## Combines ideas from ## https://stackoverflow.com/questions/402377/using-getopts-in-bash-shell-script-to-get-long-and-short-command-line-options ## 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 '"$1">&2 exit 1 } printparse(){ if [ ${VERBOSE} -gt 0 ]; then printf 'Parse: %s%s%s '"$1""$2""$3">&2; fi } showme(){ if [ ${VERBOSE} -gt 0 ]; then printf 'VERBOSE: %s '"$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[=]<value>] [--toc[=]<TRUE,FALSE>]">&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[=]<value>] [--toc[=]<TRUE,FALSE>]">&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" |
在这里,您可以在bash中找到几种不同的复杂选项解析方法:http://mywiki.wooledge.org/complexoption解析
我确实创建了下面的一个,我认为这是一个好的,因为它是最小的代码长期权和短期权都有效。使用这种方法,长选项还可以有多个参数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | #!/bin/bash # Uses bash extensions. Not portable as written. declare -A longoptspec longoptspec=( [loglevel]=1 ) #use associative array to declare how many arguments a long option expects, in this case we declare that loglevel expects/has one argument, long options that aren't listed i n this way will have zero arguments by default optspec=":h-:" while getopts"$optspec" opt; do while true; do case"${opt}" in -) #OPTARG is name-of-long-option or name-of-long-option=value if [["${OPTARG}" =~ .*=.* ]] #with this --key=value format only one argument is possible then opt=${OPTARG/=*/} OPTARG=${OPTARG#*=} ((OPTIND--)) else #with this --key value1 value2 format multiple arguments are possible opt="$OPTARG" OPTARG=(${@:OPTIND:$((longoptspec[$opt]))}) fi ((OPTIND+=longoptspec[$opt])) continue #now that opt/OPTARG are set we can process them as if getopts would've given us long options ;; loglevel) loglevel=$OPTARG ;; h|help) echo"usage: $0 [--loglevel[=]<value>]">&2 exit 2 ;; esac break; done done # End of file |
我在这方面工作了很长时间…并创建了我自己的库,您需要在主脚本中找到它。例如,请参见libopt4shell和cd2mpc。希望它有帮助!
改进的解决方案:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 | # translate long options to short # Note: This enable long options but disable"--?*" in $OPTARG, or disable long options after "--" in option fields. for ((i=1;$#;i++)) ; do case"$1" in --) # [ ${args[$((i-1))]} == ... ] || EndOpt=1 ;;& # DIRTY: we still can handle some execptions... EndOpt=1 ;;& --version) ((EndOpt)) && args[$i]="$1" || args[$i]="-V";; # default case : short option use the first char of the long option: --?*) ((EndOpt)) && args[$i]="$1" || args[$i]="-${1:2:1}";; # pass through anything else: *) args[$i]="$1" ;; esac shift done # reset the translated args set --"${args[@]}" function usage { echo"Usage: $0 [options] files">&2 exit $1 } # now we can process with getopt while getopts":hvVc:" opt; do case $opt in h) usage ;; v) VERBOSE=true ;; V) echo $Version ; exit ;; c) source $OPTARG ;; \?) echo"unrecognized option: -$opt" ; usage -1 ;; :) echo"option -$OPTARG requires an argument" usage -1 ;; esac done shift $((OPTIND-1)) [["$1" =="--" ]] && shift |
如果需要长的命令行选项,那么使用ksh可能更简单,只适用于getopts部分,因为这样做更容易。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | # Working Getopts Long => KSH #! /bin/ksh # Getopts Long USAGE="s(showconfig)" USAGE+="c:(createdb)" USAGE+="l:(createlistener)" USAGE+="g:(generatescripts)" USAGE+="r:(removedb)" USAGE+="x:(removelistener)" USAGE+="t:(createtemplate)" USAGE+="h(help)" while getopts"$USAGE" optchar ; do case $optchar in s) echo"Displaying Configuration" ;; c) echo"Creating Database $OPTARG" ;; l) echo"Creating Listener LISTENER_$OPTARG" ;; g) echo"Generating Scripts for Database $OPTARG" ;; r) echo"Removing Database $OPTARG" ;; x) echo"Removing Listener LISTENER_$OPTARG" ;; t) echo"Creating Database Template" ;; h) echo"Help" ;; esac done |
我想要一些没有外部依赖性的东西,有严格的bash支持(-u),并且我需要它在更老的bash版本上工作。它处理各种类型的参数:
- 短杆(-h)
- 短选项(-i"image.jpg")
- 长鼻(帮助)
- 等于选项(--file="filename.ext")
- 空格选项(--file"filename.ext")
- 固定的喷鼻(-hvm)
只需在脚本顶部插入以下内容:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 | # Check if a list of params contains a specific param # usage: if _param_variant"h|?|help p|path f|file long-thing t|test-thing""file" ; then ... # the global variable $key is updated to the long notation (last entry in the pipe delineated list, if applicable) _param_variant() { for param in $1 ; do local variants=${param//\|/ } for variant in $variants ; do if [["$variant" ="$2" ]] ; then # Update the key to match the long version local arr=(${param//\|/ }) let last=${#arr[@]}-1 key="${arr[$last]}" return 0 fi done done return 1 } # Get input parameters in short or long notation, with no dependencies beyond bash # usage: # # First, set your defaults # param_help=false # param_path="." # param_file=false # param_image=false # param_image_lossy=true # # Define allowed parameters # allowed_params="h|?|help p|path f|file i|image image-lossy" # # Get parameters from the arguments provided # _get_params $* # # Parameters will be converted into safe variable names like: # param_help, # param_path, # param_file, # param_image, # param_image_lossy # # Parameters without a value like"-h" or"--help" will be treated as # boolean, and will be set as param_help=true # # Parameters can accept values in the various typical ways: # -i"path/goes/here" # --image"path/goes/here" # --image="path/goes/here" # --image=path/goes/here # These would all result in effectively the same thing: # param_image="path/goes/here" # # Concatinated short parameters (boolean) are also supported # -vhm is the same as -v -h -m _get_params(){ local param_pair local key local value local shift_count while : ; do # Ensure we have a valid param. Allows this to work even in -u mode. if [[ $# == 0 || -z $1 ]] ; then break fi # Split the argument if it contains"=" param_pair=(${1//=/ }) # Remove preceeding dashes key="${param_pair[0]#--}" # Check for concatinated boolean short parameters. local nodash="${key#-}" local breakout=false if [["$nodash" !="$key" && ${#nodash} -gt 1 ]]; then # Extrapolate multiple boolean keys in single dash notation. ie."-vmh" should translate to:"-v -m -h" local short_param_count=${#nodash} let new_arg_count=$#+$short_param_count-1 local new_args="" # $str_pos is the current position in the short param string $nodash for (( str_pos=0; str_pos<new_arg_count; str_pos++ )); do # The first character becomes the current key if [ $str_pos -eq 0 ] ; then key="${nodash:$str_pos:1}" breakout=true fi # $arg_pos is the current position in the constructed arguments list let arg_pos=$str_pos+1 if [ $arg_pos -gt $short_param_count ] ; then # handle other arguments let orignal_arg_number=$arg_pos-$short_param_count+1 local new_arg="${!orignal_arg_number}" else # break out our one argument into new ones local new_arg="-${nodash:$str_pos:1}" fi new_args="$new_args "$new_arg"" done # remove the preceding space and set the new arguments eval set --"${new_args# }" fi if ! $breakout ; then key="$nodash" fi # By default we expect to shift one argument at a time shift_count=1 if ["${#param_pair[@]}" -gt"1" ] ; then # This is a param with equals notation value="${param_pair[1]}" else # This is either a boolean param and there is no value, # or the value is the next command line argument # Assume the value is a boolean true, unless the next argument is found to be a value. value=true if [[ $# -gt 1 && -n"$2" ]]; then local nodash="${2#-}" if ["$nodash" ="$2" ]; then # The next argument has NO preceding dash so it is a value value="$2" shift_count=2 fi fi fi # Check that the param being passed is one of the allowed params if _param_variant"$allowed_params""$key" ; then # --key-name will now become param_key_name eval param_${key//-/_}="$value" else printf 'WARNING: Unknown option (ignored): %s '"$1">&2 fi shift $shift_count done } |
像这样使用它:
1 2 3 4 5 6 7 8 9 10 11 12 13 | # Assign defaults for parameters param_help=false param_path=$(pwd) param_file=false param_image=true param_image_lossy=true param_image_lossy_quality=85 # Define the params we will allow allowed_params="h|?|help p|path f|file i|image image-lossy image-lossy-quality" # Get the params from arguments provided _get_params $* |
这个公认的答案很好地指出了bash内置
So while it is possible to write more code to work around the lack of support for long options, this is a lot more work and partially defeats the purpose of using a getopt parser to simplify your code.
尽管原则上我同意这一说法,但我认为我们在各种脚本中实现此功能的次数足以证明我们在创建一个"标准化的、经过良好测试的解决方案时付出了一些努力。
因此,我已经"升级"了在
通过在脚本中包含
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | source"${PATH_TO}/getopts_long.bash" while getopts_long ':c: copyfile:' OPTKEY; do case ${OPTKEY} in 'c'|'copyfile') echo 'file supplied -- ${OPTARG}' ;; '?') echo"INVALID OPTION -- ${OPTARG}">&2 exit 1 ;; ':') echo"MISSING ARGUMENT for option -- ${OPTARG}">&2 exit 1 ;; *) echo"Misconfigured OPTSPEC or uncaught option -- ${OPTKEY}">&2 exit 1 ;; esac done shift $(( OPTIND - 1 )) [["${1}" =="--" ]] && shift |
我还没有足够的代表来评论或投票他的解决方案,但SME的回答对我来说非常有效。我遇到的唯一问题是,这些论点最后都用单引号括起来了(所以我把它们去掉了)。
我还添加了一些示例用法和帮助文本。我将在这里包括我的稍微扩展的版本:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 | #!/bin/bash # getopt example # from: https://stackoverflow.com/questions/402377/using-getopts-in-bash-shell-script-to-get-long-and-short-command-line-options HELP_TEXT=\ " USAGE: Accepts - and -- flags, can specify options that require a value, and can be in any order. A double-hyphen (--) will stop processing options. Accepts the following forms: getopt-example.sh -a -b -c value-for-c some-arg getopt-example.sh -c value-for-c -a -b some-arg getopt-example.sh -abc some-arg getopt-example.sh --along --blong --clong value-for-c -a -b -c some-arg getopt-example.sh some-arg --clong value-for-c getopt-example.sh " aflag=false bflag=false cargument="" # options may be followed by one colon to indicate they have a required argument if ! options=$(getopt -o abc:h\? -l along,blong,help,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=true ;; -b|--blong) bflag=true ;; # for options with required arguments, an additional shift is required -c|--clong) cargument="$2" ; shift;; -h|--help|-\?) echo -e $HELP_TEXT; exit;; (--) shift; break;; (-*) echo"$0: error - unrecognized option $1" 1>&2; exit 1;; (*) break;; esac shift done # to remove the single quotes around arguments, pipe the output into: # | sed -e"s/^'\\|'$//g" (just leading/trailing) or | sed -e"s/'//g" (all) echo aflag=${aflag} echo bflag=${bflag} echo cargument=${cargument} while [ $# -gt 0 ] do echo arg=$1 shift if [[ $aflag == true ]]; then echo a is true fi done |
如果简单地说,这就是您想要调用脚本的方式
1 | myscript.sh --input1"ABC" --input2"PQR" --input2"XYZ" |
然后,您可以使用getopt和--longoptions的帮助,按照这个最简单的方法来实现它。
试试这个,希望这个有用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 | # 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 |
如果所有长选项都具有唯一性和匹配性,则第一个字符作为短选项,例如
1 | ./slamm --chaos 23 --plenty test -quiet |
是一样的
1 | ./slamm -c 23 -p test -q |
您可以在getopts重写$args之前使用它:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | # change long options to short options for arg; do [["${arg:0:1}" =="-" ]] && delim="" || delim=""" if ["${arg:0:2}" =="--" ]; then args="${args} -${arg:2:1}" else args="${args} ${delim}${arg}${delim}" fi done # reset the incoming args eval set -- $args # proceed as usual while getopts":b:la:h" OPTION; do ..... |
感谢Mtvee的启发;-)
为了保持跨平台兼容,避免依赖外部可执行文件,我从另一种语言移植了一些代码。
我发现它很容易使用,下面是一个例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | ArgParser::addArg"[h]elp" false "This list" ArgParser::addArg"[q]uiet" false "Supress output" ArgParser::addArg"[s]leep" 1 "Seconds to sleep" ArgParser::addArg"v" 1 "Verbose mode" ArgParser::parse"$@" ArgParser::isset help && ArgParser::showArgs ArgParser::isset"quiet" \ && echo"Quiet!" \ || echo"Noisy!" local __sleep ArgParser::tryAndGetArg sleep into __sleep \ && echo"Sleep for $__sleep seconds" \ || echo"No value passed for sleep" # This way is often more convienient, but is a little slower echo"Sleep set to: $( ArgParser::getArg sleep )" |
所需的bash比它可能要长一些,但我想避免依赖bash 4的关联数组。您也可以直接从http://nt4.com/bash/argparser.inc.sh下载此文件。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 | #!/usr/bin/env bash # Updates to this script may be found at # http://nt4.com/bash/argparser.inc.sh # Example of runtime usage: # mnc.sh --nc -q Caprica.S0*mkv *.avi *.mp3 --more-options here --host centos8.host.com # Example of use in script (see bottom) # Just include this file in yours, or use # source argparser.inc.sh unset EXPLODED declare -a EXPLODED function explode { local c=$# (( c < 2 )) && { echo function"$0" is missing parameters return 1 } local delimiter="$1" local string="$2" local limit=${3-99} local tmp_delim=$'\x07' local delin=${string//$delimiter/$tmp_delim} local oldifs="$IFS" IFS="$tmp_delim" EXPLODED=($delin) IFS="$oldifs" } # See: http://fvue.nl/wiki/Bash:_Passing_variables_by_reference # Usage: local"$1" && upvar $1"value(s)" upvar() { if unset -v"$1"; then # Unset & validate varname if (( $# == 2 )); then eval $1="\$2" # Return single value else eval $1=\("\${@:2}"\) # Return array fi fi } function decho { : } function ArgParser::check { __args=${#__argparser__arglist[@]} for (( i=0; i<__args; i++ )) do matched=0 explode"|""${__argparser__arglist[$i]}" if ["${#1}" -eq 1 ] then if ["${1}" =="${EXPLODED[0]}" ] then decho"Matched $1 with ${EXPLODED[0]}" matched=1 break fi else if ["${1}" =="${EXPLODED[1]}" ] then decho"Matched $1 with ${EXPLODED[1]}" matched=1 break fi fi done (( matched == 0 )) && return 2 # decho"Key $key has default argument of ${EXPLODED[3]}" if ["${EXPLODED[3]}" =="false" ] then return 0 else return 1 fi } function ArgParser::set { key=$3 value="${1:-true}" declare -g __argpassed__$key="$value" } function ArgParser::parse { unset __argparser__argv __argparser__argv=() # echo parsing:"$@" while [ -n"$1" ] do # echo"Processing $1" if ["${1:0:2}" == '--' ] then key=${1:2} value=$2 elif ["${1:0:1}" == '-' ] then key=${1:1} # Strip off leading - value=$2 else decho"Not argument or option: '$1'">& 2 __argparser__argv+=("$1" ) shift continue fi # parameter=${tmp%%=*} # Extract name. # value=${tmp##*=} # Extract value. decho"Key: '$key', value: '$value'" # eval $parameter=$value ArgParser::check $key el=$? # echo"Check returned $el for $key" [ $el -eq 2 ] && decho"No match for option '$1'">&2 # && __argparser__argv+=("$1" ) [ $el -eq 0 ] && decho"Matched option '${EXPLODED[2]}' with no arguments" >&2 && ArgParser::set true"${EXPLODED[@]}" [ $el -eq 1 ] && decho"Matched option '${EXPLODED[2]}' with an argument of '$2'" >&2 && ArgParser::set"$2""${EXPLODED[@]}" && shift shift done } function ArgParser::isset { declare -p"__argpassed__$1"> /dev/null 2>&1 && return 0 return 1 } function ArgParser::getArg { # This one would be a bit silly, since we can only return non-integer arguments ineffeciently varname="__argpassed__$1" echo"${!varname}" } ## # usage: tryAndGetArg into <varname> # returns: 0 on success, 1 on failure function ArgParser::tryAndGetArg { local __varname="__argpassed__$1" local __value="${!__varname}" test -z"$__value" && return 1 local"$3" && upvar $3"$__value" return 0 } function ArgParser::__construct { unset __argparser__arglist # declare -a __argparser__arglist } ## # @brief add command line argument # @param 1 short and/or long, eg: [s]hort # @param 2 default value # @param 3 description ## function ArgParser::addArg { # check for short arg within long arg if [["$1" =~ \[(.)\] ]] then short=${BASH_REMATCH[1]} long=${1/\[$short\]/$short} else long=$1 fi if ["${#long}" -eq 1 ] then short=$long long='' fi decho short:"$short" decho long:"$long" __argparser__arglist+=("$short|$long|$1|$2|$3") } ## # @brief show available command line arguments ## function ArgParser::showArgs { # declare -p | grep argparser printf"Usage: %s [OPTION...] ""$( basename"${BASH_SOURCE[0]}" )" printf"Defaults for the options are specified in brackets. "; __args=${#__argparser__arglist[@]} for (( i=0; i<__args; i++ )) do local shortname= local fullname= local default= local description= local comma= explode"|""${__argparser__arglist[$i]}" shortname="${EXPLODED[0]:+-${EXPLODED[0]}}" # String Substitution Guide: fullname="${EXPLODED[1]:+--${EXPLODED[1]}}" # http://tldp.org/LDP/abs/html/parameter-substitution.html test -n"$shortname" \ && test -n"$fullname" \ && comma="," default="${EXPLODED[3]}" case $default in false ) default= ;; "" ) default= ;; * ) default="[$default]" esac description="${EXPLODED[4]}" printf" %2s%1s %-19s %s %s ""$shortname""$comma""$fullname""$description""$default" done } function ArgParser::test { # Arguments with a default of 'false' do not take paramaters (note: default # values are not applied in this release) ArgParser::addArg"[h]elp" false "This list" ArgParser::addArg"[q]uiet" false "Supress output" ArgParser::addArg"[s]leep" 1 "Seconds to sleep" ArgParser::addArg"v" 1 "Verbose mode" ArgParser::parse"$@" ArgParser::isset help && ArgParser::showArgs ArgParser::isset"quiet" \ && echo"Quiet!" \ || echo"Noisy!" local __sleep ArgParser::tryAndGetArg sleep into __sleep \ && echo"Sleep for $__sleep seconds" \ || echo"No value passed for sleep" # This way is often more convienient, but is a little slower echo"Sleep set to: $( ArgParser::getArg sleep )" echo"Remaining command line: ${__argparser__argv[@]}" } if ["$( basename"$0" )" =="argparser.inc.sh" ] then ArgParser::test"$@" fi |
EasyOptions处理短选项和长选项:
1 2 3 4 5 6 7 8 9 10 | ## Options: ## --verbose, -v Verbose mode ## --logfile=NAME Log filename source easyoptions || exit if test -n"${verbose}"; then echo"log file: ${logfile}" echo"arguments: ${arguments[@]}" fi |
builtin
以下是http://www.uxora.com/unix/shell-script/22-handle-long-options-with-getopts中的一部分代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 | #== set short options ==# SCRIPT_OPTS=':fbF:B:-:h' #== set long options associated with short one ==# 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)) |
这是一个测试:
1 2 3 4 5 6 7 8 9 10 11 12 13 | # 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中,
内置OSX(BSD)getopt不支持长选项,但GNU版本支持:
一个简单的DIY只获得长名称args:
用途:
1 2 3 4 5 6 7 8 | $ ./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 ="" |
脚本:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 | #!/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"$@" |
getopts"could be used"用于解析长选项,只要您不希望它们有参数…
以下是如何:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | $ cat > longopt while getopts 'e:-:' OPT; do case $OPT in e) echo echo: $OPTARG;; -) #long option case $OPTARG in long-option) echo long option;; *) echo long option: $OPTARG;; esac;; esac done $ bash longopt -e asd --long-option --long1 --long2 -e test echo: asd long option long option: long1 long option: long2 echo: test |
如果尝试使用optind获取long选项的参数,getopts会将其视为第一个非可选位置参数,并停止分析任何其他参数。在这种情况下,您最好用一个简单的case语句手动处理它。
这将"始终"起作用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | $ cat >longopt2 while (($#)); do OPT=$1 shift case $OPT in --*) case ${OPT:2} in long1) echo long1 option;; complex) echo comples with argument $1; shift;; esac;; -*) case ${OPT:1} in a) echo short option a;; b) echo short option b with parameter $1; shift;; esac;; esac done $ bash longopt2 --complex abc -a --long -b test comples with argument abc short option a short option b with parameter test |
尽管不像getopts那么灵活,但是您必须自己在case实例中检查代码…
但这是一种选择。
嗯。
对纯bash选项不太满意。为什么不使用Perl来得到你想要的呢?直接分析$*数组,并自动命名选项。
简单帮助程序脚本:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | #!/usr/bin/perl use Getopt::Long; my $optstring = shift; my @opts = split(m#,#, $optstring); my %opt; GetOptions(\%opt, @opts); print"set --" . join(' ', map("'$_'", @ARGV)) .";"; my $xx; my $key; foreach $key (keys(%opt)) { print"export $key='$opt{$key}';"; } |
然后,您可以在脚本中使用一行程序,例如:
1 2 3 4 5 6 7 8 9 | #!/bin/bash eval `getopts.pl reuse:s,long_opt:s,hello $*`; echo"HELLO: $hello" echo"LONG_OPT: $long_opt" echo"REUSE: $reuse" echo $* |
/tmp/script.sh hello——重用我——long——选择你想要的任何东西——除了空格——hello 1 2 3
你好:1long-opt:你想要的任何东西,除了空格再利用:我
1 2 2
这里唯一要注意的是空间不起作用。但它避免了bash相当复杂的循环语法,可以使用长参数,自动将它们命名为变量,并自动调整$*的大小,因此99%的时间都可以使用。