在bash shell脚本中使用getopts来获取长短命令行选项

我希望使用我的shell脚本来调用长短命令行选项。

我知道可以使用getopts ,但是就像在Perl中一样,我还没有能够对shell做同样的事情。

任何想法如何做到这一点,以便我可以使用如下选项:

 ./shell.sh --copyfile abc.pl /tmp/ ./shell.sh -c abc.pl /tmp/ 

在上面,这两个命令对我的shell意味着同样的事情,但使用getopts ,我还没有能够实现这些?

bash getopts内build不支持带有双折线前缀的长选项名称。 它只支持单字符选项。

有一个shell工具getopt是另一个程序,而不是一个bash内build的。 getopt(3)的GNU实现getopt(3)在Linux上由命令行getopt(1)使用)支持parsing长选项。

但是getopt的BSD实现(例如在Mac OS X上)并没有。

getoptgetopts是不同的动物,人们似乎对他们所做的一些误解。 getopts是一个内置命令,用于在循环中处理命令行选项,并将每个find的选项和值依次分配给内置variables,以便进一步处理它们。 然而, getopt是一个外部实用程序,它并不像 bash getopts ,Perl Getopt模块或Python optparse / argparse模块那样为您处理选项getopt所做的就是规范化传入的选项,即将它们转换为更标准的forms,以便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行的所有内容),代码仍然可以工作 ! 但是,你的代码在接受什么types的选项时会更挑剔:特别是,你必须在上面描述的“规范”forms中指定所有的选项。 但是,通过使用getopt ,可以将单字母选项分组,使用较长的非选项forms,使用--file foo.txt--file=foo.txt样式,使用-m 4096-m4096样式,以任意顺序混合选项和非选项等。如果找不到无法识别的或不明确的选项, getopt也会输出错误消息。

注意 :实际上有两个完全不同getopt版本,基本的getopt和GNU getopt ,具有不同的function和不同的调用约定。 2基本的getopt是相当破碎的:它不仅不能处理长选项,它甚至不能处理参数内部的embedded空格或空参数,而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位于/usr/bin之前的shellpath中。 在FreeBSD上,安装misc/getopt

修改您自己的程序的示例代码的快速指南:在前几行中,除了调用getopt那行之外,所有这些都应该保持不变。 您应该在-n后更改程序名称,在-o之后指定短的选项,在--long之后指定长的选项。 把一个冒号后面的选项取值。

最后,如果你看到刚刚set代码而不是eval set ,那么它就是为BSD getopt编写的。 您应该将其更改为使用eval set样式,对于两个版本的getopt都可以正常工作,而普通set在GNU getopt不起作用。

1实际上, ksh93 getopts支持长命名的选项,但是这个shell并不像bash那样经常使用。 在zsh ,使用zparseopts来获得这个function。

2从技术上讲,“GNU getopt ”是一个用词不当, 这个版本实际上是为Linux而不是GNU项目编写的。 但是,它遵循所有的GNU惯例,而且“GNU getopt ”这个术语是常用的(例如在FreeBSD上)。

Bash内build的getopts函数可以用来parsing长选项,方法是将一个破折号字符和一个冒号放入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[=]<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 

复制到当前工作目录中的可执行文件名称= 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[=]<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既不执行OPTERR检查,也不执行长选项的选项参数分析。 上面的脚本片段显示了如何手动完成。 基本原理也适用于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指出的那样,这个技巧利用了允许选项参数(即“-f filename”中的文件名)的shell的非标准行为。连接到选项(如“-ffilename”)。 POSIX标准说明它们之间必须有一个空格,在“ – longoption”的情况下,将终止选项parsing并将所有longoption变成非选项参数。

内置的getopts命令仍然是AFAIK,仅限于单字符选项。

有(或曾经是)外部程序getopt将重新组织一组选项,以便更容易parsing。 你可以适应这种devise来处理长期的select。 用法示例:

 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命令使用类似的scheme。

请注意,外部getopt程序的根本弱点是处理其中有空格的参数以及准确保留这些空格的困难。 这就是为什么内置的getopts是优越的,虽然受限于它只处理单字母选项的事实。

下面是一个实际使用getopt和long选项的例子:

 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 

看看shFlags是一个可移植的shell库(意思是:sh,bash,dash,ksh,zsh在Linux,Solaris等)。

它增加了新的标志,就像在脚本中添加一行一样简单,它提供了一个自动生成的使用函数。

这里是一个简单的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}!" 

对于支持长期选项(例如Linux)的增强型getopt的操作系统,您可以执行以下操作:

 $ ./hello_world.sh --name Kate Hello, Kate! 

其余的,你必须使用简短的选项:

 $ ./hello_world.sh -n Kate Hello, Kate! 

添加一个新标志就像添加一个新的DEFINE_ call一样简单。

长选项可以被标准的getopts内buildparsing为“参数” - “选项”

这是可移植的本地POSIXshell – 不需要外部程序或bashisms。

本指南将长选项作为-选项的参数,所以--alphagetopts视为-带参数alpha- bravo=foo被视为-带参数bravo=foo 。 真正的参数可以用一个简单的replace来收获: ${OPTARG#*=}

在这个例子中, -b (及其长格式,– --bravo )有一个强制选项(注意长格式的强制执行的手动重构)。 对于长参数的非布尔选项来自等号,例如--bravo=foo (长选项的空格分隔符很难实现)。

因为它使用getopts ,所以这个解决scheme支持像cmd -ac --bravo=foo -d FILE这样的用法(它将选项-ac结合在一起,并将长选项与标准选项相交错),而这里的大多数答案要么就是要么做不到那。

 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手动实现长选项,所以需要一些pipe家:

  • bravo=? 匹配--bravo=foo但不是--bravo= (注意: case在第一次匹配后停止)
  • bravo*跟随,注意在--bravo--bravo=缺less必要的参数
  • alpha* | charlie* alpha* | charlie*捕获给予不支持它们的选项的参数
  • ''是为了支持碰巧以破折号开始的非选项
  • *捕获所有其他长选项,并重新为getopts抛出一个无效选项抛出的错误

你不一定需要所有这些家务pipe理物品。 例如,也许你想让--bravo有一个可选的参数(由于getopts的限制, -b不能支持)。 只是删除=? 和相关的失败案例,然后在第一次使用$ARG_B时调用${ARG_B:=$DEFAULT_ARG_B}

使用短/长选项和参数的getopts


适用于所有组合,eG:

  • foob​​ar -f –bar
  • foob​​ar –foo -b
  • foob​​ar -bf –bar –foobar
  • foob​​ar -fbFBAshorty –bar -FB –arguments = longhorn
  • foob​​ar -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 

Usage函数的外观

 function _usage() { ###### USAGE : 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 " 

具有长/短标志以及长参数的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 these 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() { ###### USAGE : 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 

我有种解决方法:

 # 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; 

我是愚蠢的还是什么? getoptgetopts是如此混乱。

其他方式…

 # 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 

如果你不想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),那么你需要手动添加这些。

但是,如果您希望获得getoptsfunction以及长期选项,这是一个简单的方法。

我也把这个片段放在一个要点上 。

The built-in getopts can't do this. There is an external getopt (1) program that can do this, but you only get it on Linux from the util-linux package. It comes with an example script getopt-parse.bash .

There is also a getopts_long written as a shell function.

 #!/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 

In ksh93 , getopts does support long names…

 while getopts "f(file):s(server):" flag do echo "$flag" $OPTIND $OPTARG done 

Or so the tutorials I have found have said. Try it and see.

Inventing yet another version of the wheel…

This function is a (hopefully) POSIX-compatible plain bourne shell replacement for GNU getopt. It supports short/long options which can accept mandatory/optional/no arguments, and the way in which options are specified is almost identical to GNU getopt, so conversion is trivial.

Of course this is still a sizeable chunk of code to drop into a script, but it's about half the lines of the well-known getopt_long shell function, and might be preferable in cases where you just want to replace existing GNU getopt uses.

This is pretty new code, so YMMV (and definitely please let me know if this isn't actually POSIX-compatible for any reason — portability was the intention from the outset, but I don't have a useful POSIX test environment).

Code and example usage follows:

 #!/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 

Here you can find a few different approaches for complex option parsing in bash: http://mywiki.wooledge.org/ComplexOptionParsing

I did create the following one, and I think it's a good one, because it's minimal code and both long and short options work. A long option can also have multiple arguments with this approach.

 #!/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 in 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 

I have been working on that subject for quite a long time… and made my own library which you will need to source in your main script. See libopt4shell and cd2mpc for an example. 希望它有帮助!

An improved solution:

 # 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 

Maybe it's simpler to use ksh, just for the getopts part, if need long command line options, as it can be easier done there.

 # 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 

I wanted something without external dependencies, with strict bash support (-u), and I needed it to work on even the older bash versions. This handles various types of params:

  • short bools (-h)
  • short options (-i "image.jpg")
  • long bools (–help)
  • equals options (–file="filename.ext")
  • space options (–file "filename.ext")
  • concatinated bools (-hvm)

Just insert the following at the top of your script:

 # 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\n' "$1" >&2 fi shift $shift_count done } 

And use it like so:

 # 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 $* 

I don't have enough rep yet to comment or vote his solution up, but sme's answer worked extremely well for me. The only issue I ran into was that the arguments end up wrapped in single-quotes (so I have an strip them out).

I also added some example usages and HELP text. I'll included my slightly extended version here:

 #!/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:\n Accepts - and -- flags, can specify options that require a value, and can be in any order. A double-hyphen (--) will stop processing options.\n\n Accepts the following forms:\n\n getopt-example.sh -a -b -c value-for-c some-arg\n getopt-example.sh -c value-for-c -a -b some-arg\n getopt-example.sh -abc some-arg\n getopt-example.sh --along --blong --clong value-for-c -a -b -c some-arg\n getopt-example.sh some-arg --clong value-for-c\n 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 

I only write shell scripts now and then and fall out of practice, so any feedback is appreciated.

Using the strategy proposed by @Arvid Requate, we noticed some user errors. A user who forgets to include a value will accidentally have the next option's name treated as a value:

 ./getopts_test.sh --loglevel= --toc=TRUE 

will cause the value of "loglevel" to be seen as "–toc=TRUE". This can be avoided.

I adapted some ideas about checking user error for CLI from http://mwiki.wooledge.org/BashFAQ/035 discussion of manual parsing. I inserted error checking into handling both "-" and "–" arguments.

Then I started fiddling around with the syntax, so any errors in here are strictly my fault, not the original authors.

My approach helps users who prefer to enter long with or without the equal sign. That is, it should have same response to "–loglevel 9" as "–loglevel=9". In the –/space method, it is not possible to know for sure if the user forgets an argument, so some guessing is needed.

  1. if the user has the long/equal sign format (–opt=), then a space after = triggers an error because an argument was not supplied.
  2. if user has long/space arguments (–opt ), this script causes a fail if no argument follows (end of command) or if argument begins with dash)

In case you are starting out on this, there is an interesting difference between "–opt=value" and "–opt value" formats. With the equal sign, the command line argument is seen as "opt=value" and the work to handle that is string parsing, to separate at the "=". In contrast, with "–opt value", the name of the argument is "opt" and we have the challenge of getting the next value supplied in the command line. That's where @Arvid Requate used ${!OPTIND}, the indirect reference. I still don't understand that, well, at all, and comments in BashFAQ seem to warn against that style ( http://mywiki.wooledge.org/BashFAQ/006 ). BTW, I don't think previous poster's comments about importance of OPTIND=$(( $OPTIND + 1 )) are correct. I mean to say, I see no harm from omitting it.

In newest version of this script, flag -v means VERBOSE printout.

Save it in a file called "cli-5.sh", make executable, and any of these will work, or fail in the desired way

 ./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 

Here is example output of the error-checking on user 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 

You should consider turning on -v, because it prints out internals of OPTIND and OPTARG

 #/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\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[=]<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" 

In order to stay cross-platform compatible, and avoid the reliance on external executables, I ported some code from another language.

I find it very easy to use, here is an example:

 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 )" 

The required BASH is a little longer than it could be, but I wanted to avoid reliance on BASH 4's associative arrays. You can also download this directly from http://nt4.com/bash/argparser.inc.sh

 #!/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 <argname> 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...]\n\n" "$( basename "${BASH_SOURCE[0]}" )" printf "Defaults for the options are specified in brackets.\n\n"; __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\n" "$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 

If all your long options have unique, and matching, first characters as the short options, so for example

 ./slamm --chaos 23 --plenty test -quiet 

是相同的

 ./slamm -c 23 -p test -q 

You can use this before getopts to rewrite $args:

 # 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 ..... 

Thanks for mtvee for the inspiration 😉

Builtin getopts only parse short options (except in ksh93), but you can still add few lines of scripting to make getopts handles long options.

Here is a part of code found in http://www.uxora.com/unix/shell-script/22-handle-long-options-with-getopts

  #== 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)) 

这是一个testing:

 # 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 

Otherwise in recent Korn Shell ksh93, getopts can naturally parse long options and even display a man page alike. (See http://www.uxora.com/unix/shell-script/20-getopts-with-man-page-and-long-options )

Th built-in OS X (BSD) getopt does not support long options, but the GNU version does: brew install gnu-getopt . Then, something similar to: cp /usr/local/Cellar/gnu-getopt/1.1.6/bin/getopt /usr/local/bin/gnu-getopt .

EasyOptions handles short and long options:

 ## 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 

getopts "could be used" for parsing long options as long as you don't expect them to have arguments…

Here's how to:

 $ 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 

If you try to use OPTIND for getting a parameter for the long option, getopts will treat it as the first no optional positional parameter and will stop parsing any other parameters. In such a case you'll be better off handling it manually with a simple case statement.

This will "always" work:

 $ 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 

Albeit is not as flexible as getopts and you have to do much of the error checking code yourself within the case instances…

But it is an option.

hm.

not really satisfied with the pure bash options. why not use perl to get what you want. Directly parse the $* array, and auto-name your options.

simple helper script:

 #!/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}'; "; } 

then you can use in your script as a one liner, for example:

 #!/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 –reuse me –long_opt whatever_you_want_except_spaces –hello 1 2 3

HELLO: 1 LONG_OPT: whatever_you_want_except spaces REUSE: me

1 2 3

Only caveat here is spaces don't work. But it avoids bash's rather complicated looping syntax, works with long args, auto-names them as variables and automatically resizes $*, so will work 99% of the time.