使用getopts处理长和短命令行选项


410

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

我知道getopts可以使用,但是像在Perl中一样,我无法对shell进行同样的操作。

关于如何完成此操作的任何想法,这样我就可以使用以下选项:

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

在上面,这两个命令对我的shell来说意义相同,但是使用 getopts,我无法实现这些功能吗?


2
恕我直言,公认的答案不是最好的答案。它没有显示如何使用getopts来处理“-”和“-”参数,就像@Arvid Requate演示的那样。我将使用类似的概念插入另一个答案,但还会处理“忘记”以为所需参数插入值的用户错误。重点:可以使getopts正常工作。如果需要跨平台的可移植性,用户应避免使用“ getopt”。另外,getopts是POSIX Shell标准的一部分,因此很可能是可移植的。
pauljohn32

Answers:


304

可以考虑以下三种实现:

  • 内置Bash getopts。这不支持带有双破折号前缀的长选项名称。它仅支持单字符选项。

  • 独立getopt命令的BSD UNIX实现(MacOS使用的命令)。这也不支持长选项。

  • GNU独立实现getopt。GNU getopt(3)getopt(1)在Linux上由命令行使用)支持解析长选项。


其他一些答案显示了使用内置的bash getopts模仿长选项的解决方案。该解决方案实际上提供了一个简短的选项,其字符为“-”。因此,您将获得“-”作为标志。然后,随后的所有内容将变为OPTARG,然后使用嵌套测试OPTARGcase

这很聪明,但有一些警告:

  • getopts无法执行opt规范。如果用户提供的选项无效,则不会返回错误。解析OPTARG时,您必须进行自己的错误检查。
  • OPTARG用于长选项名称,当长选项本身具有参数时,会使用法复杂化。您最终不得不自己编写代码,作为另外一种情况。

因此,尽管有可能编写更多代码来解决对长选项缺乏支持的问题,但这需要做更多的工作,并且在一定程度上抵消了使用getopt解析器简化代码的目的。


17
所以。什么是跨平台的便携式解决方案?
troelskn

6
GNU Getopt似乎是唯一的选择。在Mac上,从macports安装GNU getopt。在Windows上,我将在Cygwin上安装GNU getopt。
比尔·卡温

2
显然,ksh getopts 可以处理长选项。
Tgr 2010年

1
@Bill +1,尽管在Mac上从源代码(software.frodo.looijaard.name/getopt)构建getopt也相当简单。您还可以使用“ getopt -T; echo $?”从脚本中检查系统上安装的getopt的版本。
Chinasaur's

8
@Bill Karwin:“内置的bash getopts不支持带有双破折号前缀的长选项名称。” 但是可以使getopts支持长选项:请参见下面的stackoverflow.com/a/7680682/915044
TomRoche 2014年

305

getopt并且getopts是不同的野兽,人们似乎对自己的行为有些误解。 getopts是一个内置命令,bash用于循环处理命令行选项,并将找到的每个选项和值依次分配给内置变量,因此您可以进一步处理它们。 getopt但是,它是一个外部实用程序,它实际上并不像 bash getopts,Perl Getopt模块或Python optparse/ argparse模块那样为您处理选项getopt所做的全部工作就是规范化传入的选项,即将它们转换为更标准的形式,以便Shell脚本更容易处理它们。例如,的应用程序getopt可能会转换以下内容:

myscript -ab infile.txt -ooutfile.txt

到这个:

myscript -a -b -o outfile.txt infile.txt

您必须自己进行实际处理。getopt如果对指定选项的方式进行了各种限制,则根本不需要使用:

  • 每个参数只放一个选项;
  • 所有选项都在任何位置参数(即非选项参数)之前;
  • 对于带有值的选项(例如,-o上面的值),该值必须作为一个单独的参数(在空格之后)。

为什么用getopt代替getopts?根本原因是只有GNU才getopt支持长名称的命令行选项。1(GNU getopt是Linux上的默认设置。Mac OS X和FreeBSD带有基本功能getopt,但不是很有用,但是可以安装GNU版本;请参见下文。)

例如,这getopt是我的脚本中使用GNU的示例javawrap

# NOTE: This requires GNU getopt.  On Mac OS X and FreeBSD, you have to install this
# separately; see below.
TEMP=`getopt -o vdm: --long verbose,debug,memory:,debugfile:,minheap:,maxheap: \
             -n 'javawrap' -- "$@"`

if [ $? != 0 ] ; then echo "Terminating..." >&2 ; exit 1 ; fi

# Note the quotes around `$TEMP': they are essential!
eval set -- "$TEMP"

VERBOSE=false
DEBUG=false
MEMORY=
DEBUGFILE=
JAVA_MISC_OPT=
while true; do
  case "$1" in
    -v | --verbose ) VERBOSE=true; shift ;;
    -d | --debug ) DEBUG=true; shift ;;
    -m | --memory ) MEMORY="$2"; shift 2 ;;
    --debugfile ) DEBUGFILE="$2"; shift 2 ;;
    --minheap )
      JAVA_MISC_OPT="$JAVA_MISC_OPT -XX:MinHeapFreeRatio=$2"; shift 2 ;;
    --maxheap )
      JAVA_MISC_OPT="$JAVA_MISC_OPT -XX:MaxHeapFreeRatio=$2"; shift 2 ;;
    -- ) shift; break ;;
    * ) break ;;
  esac
done

这使您可以指定类似--verbose -dm4096 --minh=20 --maxhe 40 --debugfi="/Users/John Johnson/debug.txt"或类似的选项。调用的效果getopt是将选项标准化,--verbose -d -m 4096 --minheap 20 --maxheap 40 --debugfile "/Users/John Johnson/debug.txt"以便您可以更轻松地处理它们。引号"$1""$2"很重要,因为它可以确保正确处理带空格的参数。

如果删除前9行(行中的所有内容eval set),该代码仍然有效!但是,您的代码在接受哪种类型的选项时会更加挑剔:尤其是,您必须以上述“规范”形式指定所有选项。getopt但是,通过使用,您可以对单个字母选项进行分组,使用较短的无歧义形式的长选项,使用--file foo.txt--file=foo.txt样式,使用-m 4096-m4096样式,以任意顺序混合选项和非选项,等等。 getopt如果发现无法识别或模棱两可的选项,还会输出一条错误消息。

注意:实际上,basic 和GNU 有两个完全不同的版本,具有不同的功能和不同的调用约定。2 Basic 非常不完善:它不仅不能处理长选项,而且甚至不能处理参数或空参数内的嵌入式空格,而这样做确实正确。上面的代码在basic中不起作用。GNU 默认安装在Linux上,但是在Mac OS X和FreeBSD上,它需要单独安装。在Mac OS X上,安装MacPorts(http://www.macports.org),然后安装GNU (通常安装到),并确保安装了getoptgetoptgetoptgetoptgetoptsgetoptgetoptsudo port install getoptgetopt/opt/local/bin/opt/local/bin路径位于您的Shell路径之前。/usr/bin。在FreeBSD上,安装misc/getopt

修改您自己的程序的示例代码的快速指南:在前几行中,除调用的行外,所有内容均应与“样板”保持相同getopt。您应在之后更改程序名称,在之后-n指定短选项,在之后指定-o长选项--long。在带有值的选项后加一个冒号。

最后,如果您看到的是set而不是的代码,则eval set它是为BSD编写的getopt。您应该更改它以使用eval set样式,该样式在的两个版本中都可以正常使用getopt,而普通样式set不适用于GNU getopt

1实际上,getoptsin ksh93支持长名称选项,但是该shell的使用频率不高bash。在中zsh,用于zparseopts获取此功能。

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


3
这非常有用,当我想向bash脚本中添加长样式选项时,使用getopt检查选项,然后在一个非常简单的循环中处理这些选项的想法非常有效。谢谢。
ianmjones 2011年

2
getoptLinux上的操作系统不是 GNU实用程序,而传统的语言getopt最初并不是来自BSD,而是来自AT&T Unix。ksh93 getopts(也来自AT&T)支持GNU样式的长选项。
Stephane Chazelas 2013年

@StephaneChazelas-编辑以反映您的评论。我仍然更喜欢“ GNU getopt”一词,尽管它用词不当,因为该版本遵循GNU约定并且通常像GNU程序一样工作(例如,使用POSIXLY_CORRECT),而“ Linux增强的getopt”错误地表明该版本仅存在于Linux。
Urban Vagabond

1
它来自util-linux软件包,因此它仅适用于Linux,因为该捆绑软件仅适用于Linux(getopt可以轻松移植到其他Unices,但其中的许多其他软件util-linux特定于Linux)。所有使用GNU getopt(3)的非GNU程序均可以理解$POSIX_CORRECT。例如,您不会aplay仅仅基于这些理由就说GNU。我怀疑当FreeBSD提到GNU getopt时,它们的意思是GNU getopt(3)C API。
Stephane Chazelas 2013年

@StephaneChazelas-FreeBSD出现一条错误消息“构建依赖项:请安装GNU getopt”,该消息明确指向getoptutil,而不是getopt(3)。
Urban Vagabond

202

Bash内置的getopts函数可用于解析长选项,方法是在optspec中放置一个破折号和一个冒号:

#!/usr/bin/env bash 
optspec=":hv-:"
while getopts "$optspec" optchar; do
    case "${optchar}" in
        -)
            case "${OPTARG}" in
                loglevel)
                    val="${!OPTIND}"; OPTIND=$(( $OPTIND + 1 ))
                    echo "Parsing option: '--${OPTARG}', value: '${val}'" >&2;
                    ;;
                loglevel=*)
                    val=${OPTARG#*=}
                    opt=${OPTARG%=$val}
                    echo "Parsing option: '--${opt}', value: '${val}'" >&2
                    ;;
                *)
                    if [ "$OPTERR" = 1 ] && [ "${optspec:0:1}" != ":" ]; then
                        echo "Unknown option --${OPTARG}" >&2
                    fi
                    ;;
            esac;;
        h)
            echo "usage: $0 [-v] [--loglevel[=]<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中的可执行文件name = 后,可以产生如下输出

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

显然OPTERR,对于长选项,getopts既不执行检查也不进行选项参数解析。上面的脚本片段显示了如何手动完成此操作。基本原理也可以在Debian Almquist shell(“破折号”)中使用。请注意特殊情况:

getopts -- "-:"  ## without the option terminator "-- " bash complains about "-:"
getopts "-:"     ## this works in the Debian Almquist shell ("dash")

请注意,正如http://mywiki.wooledge.org/BashFAQ上的 GreyCat 指出的那样,此技巧利用了shell的非标准行为,该行为允许使用选项参数(即“ -f filename”中的文件名)连接到该选项(如“ -ffilename”中所示)。该POSIX标准说必须有它们之间的空间,这在的情况下“ - longoption”将终止期权分析,把所有longoptions成非选项参数。


2
一个问题:!in 的语义是val="${!OPTIND}什么?
TomRoche 2014年

2
@TomRoche,这是间接替换:unix.stackexchange.com/a/41293/84316
ecbrodie 2014年

2
@ecbrodie:这是因为实际上已经处理了两个参数,而不是一个。第一个参数是单词“日志级别”,接下来是参数说法。同时,getopts自动仅以OPTIND1 递增,但在本例中,我们需要将其递增2,因此我们手动将其getopts递增1,然后让我们自动再次使其递增1。
维克多·扎曼尼安

3
既然我们在这里进行bash平衡:算术表达式中允许使用裸变量名,而$不必这样做。OPTIND=$(( $OPTIND + 1 ))可以公正OPTIND=$(( OPTIND + 1 ))。甚至更有趣的是,您甚至可以在算术表达式内分配和增加变量,因此可以将其缩写为: $(( ++OPTIND )),甚至(( ++OPTIND ))考虑到++OPTIND始终为正数,这样就不会使使用该-e选项的shell运行失败。:-) gnu.org/software/bash/manual/html_node/Shell-Arithmetic.html
clacke

3
为什么不--very-bad发出警告?
汤姆·黑尔

148

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

有一个(或曾经是)一个外部程序getopt,它将重组一组选项,以便于解析。您也可以修改该设计以处理长选项。用法示例:

aflag=no
bflag=no
flist=""
set -- $(getopt abf: "$@")
while [ $# -gt 0 ]
do
    case "$1" in
    (-a) aflag=yes;;
    (-b) bflag=yes;;
    (-f) flist="$flist $2"; shift;;
    (--) shift; break;;
    (-*) echo "$0: error - unrecognized option $1" 1>&2; exit 1;;
    (*)  break;;
    esac
    shift
done

# Process remaining non-option arguments
...

您可以在getoptlong命令中使用类似的方案。

请注意,外部getopt程序的根本缺点是难以处理其中带有空格的参数以及准确保留这些空格的困难。这就是内置功能getopts优越的原因,尽管它仅处理单字母选项受到限制。


11
除了GNU版本(具有不同的调用约定)外,getopt基本上已损坏。不要使用它。请使用** getopts代替bash-hackers.org/wiki/doku.php/howto/getopts_tutorial
hendry

9
@hendry-通过您自己的链接:“请注意,getopts无法解析GNU风格的长选项(--myoption)或XF86风格的长选项(-myoption)!”
Tom Auger

1
乔纳森(Jonathan)-您应该重写该示例以使用eval set引号(请参见下面的答案),以便它也可以与GNU getopt(Linux上的默认值)一起正常使用,并正确处理空格。
Urban Vagabond 2012年

@UrbanVagabond:我不确定为什么要这么做。问题被标记为Unix,而不是Linux。我故意展示了传统的机制,并且在参数等方面存在空白。如果愿意,您可以演示特定于现代Linux的版本,然后您的答案就可以了。(我注意到,各处,您的使用${1+"$@"},是古朴和在什么现代炮弹是必要的,特别是与你会发现在Linux上的任何外壳赔率参见使用$ 1:+“$ @”}在/ bin / sh的了)
乔纳森·莱夫勒

您应该这样做,因为eval set对GNU和BSD都可以做正确的事情getopt,而普通set对BSD只能做正确的事情getopt。因此,您也可以eval set鼓励人们养成这样做的习惯。顺便说一句,谢谢,我没有意识到这${1+"$@"}不再需要了。我必须编写在Mac OS X和Linux上都可以使用的东西-它们在两者之间迫使很多可移植性。我只是检查和"$@"确实做所有的正确的事情shbashksh,和zsh在Mac OS X; 当然也在Linux下。
Urban Vagabond

78

这是一个实际将getopt与长选项结合使用的示例:

aflag=no
bflag=no
cargument=none

# options may be followed by one colon to indicate they have a required argument
if ! options=$(getopt -o abc: -l along,blong,clong: -- "$@")
then
    # something went wrong, getopt will put out an error message for us
    exit 1
fi

set -- $options

while [ $# -gt 0 ]
do
    case $1 in
    -a|--along) aflag="yes" ;;
    -b|--blong) bflag="yes" ;;
    # for options with required arguments, an additional shift is required
    -c|--clong) cargument="$2" ; shift;;
    (--) shift; break;;
    (-*) echo "$0: error - unrecognized option $1" 1>&2; exit 1;;
    (*) break;;
    esac
    shift
done

1
您应该重写该示例以使用eval set引号(请参见下面的答案),以便它也可以与GNU getopt(Linux上的默认值)一起正常使用,并正确处理空格。
Urban Vagabond 2012年

1
getopt问题正在使用时正在使用getopts
尼古拉斯·伯格隆德

(--(-*(*有效的模式?他们如何从不同的---**
马兰(Maëlan)

@Maëlan -领先的开放括号是可选的,因此(--)等同于--)一个case节。奇怪的是,该可选的前导括号的缩进不均匀且用法不一致,但是答案的当前代码对我来说是有效的。
亚当·卡兹

59

getopts内置标准可以将长选项解析为-“选项”的“参数”

这是可移植的本地POSIX外壳–无需外部程序或Bashisms。

本指南工具长选项作为参数传递给-选项,因此--alpha被看作getopts-与参数alpha--bravo=foo被视为-与参数bravo=foo。可以通过简单的替换来获取true参数${OPTARG#*=}

在此示例中,-b和和-c(及其长格式--bravo--charlie)具有强制性参数。关于长选项的争论出现在等号之后,例如--bravo=foo(长选项的空格分隔符将难以实现,请参见下文)。

因为这使用getopts内置函数,所以该解决方案支持类似的用法cmd --bravo=foo -ac FILE(具有组合选项-a-c并将长选项与标准选项交织在一起),而此处的大多数其他答案都很难或不能做到这一点。

die() { echo "$*" >&2; exit 2; }  # complain to STDERR and exit with error
needs_arg() { if [ -z "$OPTARG" ]; then die "No arg for --$OPT option"; fi; }

while getopts ab:c:-: OPT; do
  # support long options: https://stackoverflow.com/a/28466267/519360
  if [ "$OPT" = "-" ]; then   # long option: reformulate OPT and OPTARG
    OPT="${OPTARG%%=*}"       # extract long option name
    OPTARG="${OPTARG#$OPT}"   # extract long option argument (may be empty)
    OPTARG="${OPTARG#=}"      # if long option argument, remove assigning `=`
  fi
  case "$OPT" in
    a | alpha )    alpha=true ;;
    b | bravo )    needs_arg; bravo="$OPTARG" ;;
    c | charlie )  needs_arg; charlie="$OPTARG" ;;
    ??* )          die "Illegal option --$OPT" ;;  # bad long option
    \? )           exit 2 ;;  # bad short option (error reported via getopts)
  esac
done
shift $((OPTIND-1)) # remove parsed options and args from $@ list

如果选项是破折号(-),则它是一个长选项。getopts将已解析的实际长选项进入$OPTARG,例如--bravo=foo原本套OPT='-'OPTARG='bravo=foo'。该if节套$OPT到的内容$OPTARG的第一等号之前(bravo在我们的例子),然后除去,从开始时$OPTARG(产生=foo在该步骤中,或空字符串如果没有=)。最后,我们去除论点的前导=。此时,$OPT是短选项(一个字符)或长选项(2个以上字符)。

case随后比赛无论是短期或长期的选项。对于短选项,会getopts自动抱怨选项和参数丢失,因此我们必须使用needs_arg函数手动复制那些选项,当该函数$OPTARG为空时会致命地退出。该??*条件将匹配所有剩余的长选项(?匹配单个字符并*匹配零个或多个,因此??*匹配2个以上字符),从而使我们能够在退出前发出“非法选项”错误。

(有关全大写变量名的注释:通常,建议是保留全大写变量供系统使用。我将$OPT全大写变量保留为与之保持一致$OPTARG,但这确实违反了该约定。我认为合适,因为这是系统应该完成的事情,并且应该是安全的,因为没有使用此类变量的标准(afaik)。)


要抱怨长选项的意外参数,请模仿我们对强制参数所做的操作:使用辅助函数。只是翻转测试以在不期望出现某个参数时抱怨一个参数:

no_arg() { if [ -n "$OPTARG" ]; then die "No arg allowed for --$OPT option"; fi; }

这个答案较旧版本试图接受带有空格分隔参数的长选项,但它并不可靠;getopts可能会过早终止,前提是该参数超出了它的范围,并且手动增量$OPTIND不适用于所有shell。

这可以使用以下技术之一来完成:

然后得出类似的结论 [ $# -gt $OPTIND ] && OPTIND=$((OPTIND+1))


非常好的独立式解决方案。一个问题:既然letter-c不需要争论,使用letter-c)?不够吗?在*似乎是多余的。
菲利普·基恩斯

1
@Arne位置参数是错误的UX;它们很难理解,可选参数也很混乱。getopts停在第一个位置参数上,因为它并非旨在处理它们。这样子命令用自己的论据,例如git diff --color,所以我会解释command --foo=moo bar --baz waz为具有--foo作为一个参数command,并--baz waz作为参数(选购件)的bar子命令。这可以通过上面的代码来完成。我拒绝,--bravo -blah因为--bravo需要一个参数,并且不清楚这-blah不是另一个选择。
亚当·卡兹

1
我不同意UX:只要您限制其数量(最多2个或1个加N个相同类型),位置参数就既有用又容易。应该可以在它们之间插入关键字参数,因为用户随后可以逐步构建命令(即ls abc -la)。
Arne Babenhauserheide

1
@AdamKatz:我为此写了一篇小文章:draketo.de/english/free-software/shell-argument-parsing —包括重复读取其余参数以捕获尾随选项。
Arne Babenhauserheide

1
@ArneBabenhauserheide:我已经更新了此答案以支持以空格分隔的参数。因为它eval在POSIX shell中需要,所以它在其余答案下面列出。
亚当·卡兹

33

看一下shFlags,它是一个可移植的shell库(含义:Linux,Solaris等上的sh,bash,dash,ksh,zsh)。

它使添加新标志就像在脚本中添加一行一样简单,并且提供了自动生成的用法功能。

这是Hello, world!使用shFlag的简单方法:

#!/bin/sh

# source shflags from current directory
. ./shflags

# define a 'name' command-line string flag
DEFINE_string 'name' 'world' 'name to say hello to' 'n'

# parse the command-line
FLAGS "$@" || exit 1
eval set -- "${FLAGS_ARGV}"

# say hello
echo "Hello, ${FLAGS_name}!"

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

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

其余的,您必须使用short选项:

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

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


2
这很棒,但不幸的是我的getopt(OS X)不支持参数中的空格:/想知道是否有替代方法。
Alastair Stuart

@AlastairStuart -在OS X上确实存在替代方法。使用MacPorts安装GNU getopt(通常将其安装到/ opt / local / bin / getopt中)。
城市流浪者2012年

3
@UrbanVagabond –遗憾的是,非系统默认工具的安装对于具有足够便携性的工具来说不是可接受的要求。
Alastair Stuart

@AlastairStuart – 有关使用内置getopts而不是GNU getopt的便携式解决方案的信息,请参阅我的答案。它与基本getopts用法相同,但对于长选项有额外的迭代。
亚当·卡兹

31

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


适用于所有组合,例如:

  • 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

使用功能的外观

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 "

getops 具有长/短标志以及长参数

while getopts ':bfh-A:BF' OPTION ; do
  case "$OPTION" in
    b  ) sbar=yes                       ;;
    f  ) sfoo=yes                       ;;
    h  ) _usage                         ;;   
    A  ) sarguments=yes;sARG="$OPTARG"  ;;
    B  ) sbarfoo=yes                    ;;
    F  ) sfoobar=yes                    ;;
    -  ) [ $OPTIND -ge 1 ] && optind=$(expr $OPTIND - 1 ) || optind=$OPTIND
         eval OPTION="\$$optind"
         OPTARG=$(echo $OPTION | cut -d'=' -f2)
         OPTION=$(echo $OPTION | cut -d'=' -f1)
         case $OPTION in
             --foo       ) lfoo=yes                       ;;
             --bar       ) lbar=yes                       ;;
             --foobar    ) lfoobar=yes                    ;;
             --barfoo    ) lbarfoo=yes                    ;;
             --help      ) _usage                         ;;
             --arguments ) larguments=yes;lARG="$OPTARG"  ;; 
             * )  _usage " Long: >>>>>>>> invalid options (long) " ;;
         esac
       OPTIND=1
       shift
      ;;
    ? )  _usage "Short: >>>>>>>> invalid options (short) "  ;;
  esac
done

输出量

##################################################################
echo "----------------------------------------------------------"
echo "RESULT short-foo      : $sfoo                                    long-foo      : $lfoo"
echo "RESULT short-bar      : $sbar                                    long-bar      : $lbar"
echo "RESULT short-foobar   : $sfoobar                                 long-foobar   : $lfoobar"
echo "RESULT short-barfoo   : $sbarfoo                                 long-barfoo   : $lbarfoo"
echo "RESULT short-arguments: $sarguments  with Argument = \"$sARG\"   long-arguments: $larguments and $lARG"

将以上内容组合成一个有凝聚力的脚本

#!/bin/bash
# foobar: getopts with short and long options AND arguments

function _cleanup ()
{
  unset -f _usage _cleanup ; return 0
}

## Clear out nested functions on exit
trap _cleanup INT EXIT RETURN

###### some declarations for this example ######
Options=$@
Optnum=$#
sfoo='no '
sbar='no '
sfoobar='no '
sbarfoo='no '
sarguments='no '
sARG=empty
lfoo='no '
lbar='no '
lfoobar='no '
lbarfoo='no '
larguments='no '
lARG=empty

function _usage() 
{
  ###### U S A G E : Help and ERROR ######
  cat <<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

这不适用于一个以上的长参数(-)。似乎只为我读了第一篇。
Sinaesthetic,

@Sinaesthetic –是的,我正在使用eval长选项上间隔参数的方法,但发现它在某些shell中不可靠(尽管我希望它可以与bash一起使用,在这种情况下您不必使用eval)。请参阅我的答案以了解如何使用接受长选项参数=以及我提到的使用空间的尝试。我的解决方案不会拨打外部电话,而这个电话使用cut了几次。
亚当·卡兹

24

另一种方式...

# 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
这是否在每个$args重新分配中都需要一个空格?甚至可以在没有批评的情况下完成此操作,但是此代码将在选项和参数中丢失空格(我认为这种$delim技巧不起作用)。如果您足够小心地仅在第一次迭代中将其清空,则可以在循环set 运行for这是一个没有bashisms 的安全版本
亚当·卡兹

18

我有点这样解决:

# 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如此混乱。


1
这似乎对我有用,我不知道此方法有什么问题,但它看起来很简单,因此肯定有其他人没有使用它的原因。
Billy Moon

1
@Billy是的,这很简单,因为我不使用任何脚本来管理参数等。基本上,我将参数字符串($ @)转换为数组,然后遍历该数组。在循环中,当前值将是键,下一个将是值。就那么简单。

1
@Theodore我很高兴这对您有所帮助!我也很痛苦。如果您有兴趣,可以在此处查看其操作示例:raw.github.com/rafaelrinaldi/swf-to-html/master/swf-to-html.sh

2
绝对是我所见过的最简单的方法。我进行了一些更改,例如使用i = $((($ i + 1))代替expr,但是这个概念是不透气的。
Thomas Dignan

6
你是不是在所有的愚蠢,但你可能会丢失一个特点:getopt的(S)可以识别出是混合选项(例如: -ltr-lt -r为也-l -t -r)。而且,它还提供了一些错误处理,并提供了一种简单的方法,可以在选项处理完成后将已处理的参数移开。
Olivier Dulac 2012年

14

如果您不需要getopt依赖项,可以执行以下操作:

while test $# -gt 0
do
  case $1 in

  # Normal option processing
    -h | --help)
      # usage and help
      ;;
    -v | --version)
      # version info
      ;;
  # ...

  # Special cases
    --)
      break
      ;;
    --*)
      # error unknown (long) option $1
      ;;
    -?)
      # error unknown (short) option $1
      ;;

  # FUN STUFF HERE:
  # Split apart combined short options
    -*)
      split=$1
      shift
      set -- $(echo "$split" | cut -c 2- | sed 's/./-& /g') "$@"
      continue
      ;;

  # Done with options
    *)
      break
      ;;
  esac

  # for testing purposes:
  echo "$1"

  shift
done

当然,那么您不能使用长划线选项。而且,如果要添加缩短的版本(例如,--verbos而不是--verbose),则需要手动添加。

但是,如果您希望获得getopts带有长选项的功能,这是一种简单的方法。

我也把这个片段的要点


这似乎一次只能使用一个长选项,但它满足了我的需求。谢谢!
kingjeffrey

在特殊情况下--),似乎shift ;缺少了。目前,--它将保留为第一个非选项参数。
dgw 2014年

我认为这实际上是更好的答案,尽管dgw指出该--选项shift在其中需要一个。我说这是更好的选择,因为替代方案要么是依赖于平台的版本,getopt要么是getopts_long您必须强制在命令开始时才使用短选项(即,您先使用,getopts然后再处理长选项),而这给出了任何顺序并完全控制。
Haravikk

这个答案让我感到奇怪,为什么我们有数十个答案来完成这个工作,而除了这个绝对清晰和直接的解决方案外,仅凭其他理由,以及是否有理由证明十亿个getopt(s)用例除了证明之外,还有其他原因?自己。
Florian Heigl

11

内置getopts无法做到这一点。有一个外部的getopt(1)程序可以执行此操作,但是您只能在Linux上从util-linux软件包中获得它。它带有示例脚本getopt-parse.bash

还有一个getopts_long写为shell的函数。


3
getopt于1993年包含在FreeBSD 1.0版中,自此成为FreeBSD的一部分。因此,它被FreeBSD 4.x采纳,并包含在Apple的Darwin项目中。从OS X 10.6.8开始,Apple随附的手册页仍然与FreeBSD手册页完全相同。是的,它包含在OS X和Linux之外的其他操作系统的对象中。关于这个错误信息的答案是-1。
ghoti 2012年

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

2
一个解释会很好。第一个脚本仅接受短选项,而第二个脚本在长选项参数解析中存在错误;它的变量应该"${1:0:1}"用于参数#1,子串的索引为0,长度为1。这不允许混合使用long和long选项。
亚当·卡兹

7

在中ksh93getopts支持长名称...

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

大概我发现的教程已经说过了。试试看。


4
这是ksh93的getopts内置。除了这种语法之外,它还具有更复杂的语法,该语法还允许长选项而没有短等价项,等等。
jilles 2011年

2
一个合理的答案。OP未指定WHAT Shell。
ghoti 2012年

6

我不时只写shell脚本,但并没有实践,因此,感谢您提供任何反馈。

使用@Arvid Requate提出的策略,我们注意到了一些用户错误。忘记包含值的用户将不小心将下一个选项的名称视为值:

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

将导致“ loglevel”的值被视为“ --toc = TRUE”。这是可以避免的。

我从http://mwiki.wooledge.org/BashFAQ/035修改了一些有关检查CLI的用户错误的想法 关于手动解析的讨论中采纳了。我将错误检查插入到处理“-”和“-”参数中。

然后,我开始摆弄语法,所以这里的任何错误都是我的错,而不是原始作者。

我的方法可以帮助希望长时间输入带有或不带有等号的用户。也就是说,它对“ --loglevel 9”的响应应该与“ --loglevel = 9”相同。在-/ space方法中,无法确定用户是否忘记了参数,因此需要进行一些猜测。

  1. 如果用户使用长/等号格式(--opt =),则=后面的空格会触发错误,因为未提供参数。
  2. 如果用户具有长/空格参数(--opt),则该脚本将导致失败,如果没有参数跟随(命令末尾)或参数以破折号开头)

如果您刚开始,“-opt = value”和“ --opt value”格式之间会有一个有趣的区别。带有等号的命令行参数被视为“ opt = value”,而要处理的工作是字符串解析,以“ =”分隔。相反,使用“ --opt值”时,参数的名称为“ opt”,因此我们面临着在命令行中获取下一个值的挑战。这就是@Arvid Requate使用$ {!OPTIND}(间接引用)的地方。我仍然完全不了解,BashFAQ中的评论似乎警告了这种风格(http://mywiki.wooledge.org/BashFAQ/006)。顺便说一句,我不认为以前的海报有关OPTIND = $((($ OPTIND + 1))的重要性的评论是正确的。我是说

在此脚本的最新版本中,标志-v表示VERBOSE打印输出。

将其保存在名为“ cli-5.sh”的文件中,使其成为可执行文件,这些文件中的任何一个都将起作用,或者以所需的方式失败

./cli-5.sh  -v --loglevel=44 --toc  TRUE
./cli-5.sh  -v --loglevel=44 --toc=TRUE
./cli-5.sh --loglevel 7
./cli-5.sh --loglevel=8
./cli-5.sh -l9

./cli-5.sh  --toc FALSE --loglevel=77
./cli-5.sh  --toc=FALSE --loglevel=77

./cli-5.sh   -l99 -t yyy
./cli-5.sh   -l 99 -t yyy

这是对intpu用户进行错误检查的示例输出

$ ./cli-5.sh  --toc --loglevel=77
ERROR: toc value must not have dash at beginning
$ ./cli-5.sh  --toc= --loglevel=77
ERROR: value for toc undefined

您应该考虑打开-v,因为它会打印出OPTIND和OPTARG的内部信息

#/usr/bin/env bash

## Paul Johnson
## 20171016
##

## Combines ideas from
## /programming/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"

OPTIND=$(( $OPTIND + 1 ))::每当您“吞噬” OPTIND的参数时(例如,当使用过的--toc value :值位于参数编号$ OPTIND中时)就需要。一旦检索到toc的值,就应该告诉getopts下一个要解析的参数不是值,但是它后面的一个(因此:: OPTIND=$(( $OPTIND + 1 )) 。和您的脚本(以及您引用的脚本)丢失了,在完成之后:(shift $(( $OPTIND -1 ))由于将参数1解析为OPTIND-1后退出了getopts,因此您需要将它们移出$@现在是任何剩余的“非选项”参数
Olivier Dulac

哦,当您转移自己时,您会“转移” getopts下面的参数,所以OPTIND总是指向正确的东西……但是我发现这很令人困惑。我相信(现在无法测试您的脚本)在getopts while循环之后您仍然需要移位$((($ OPTIND-1)),因此$ 1现在不指向原始$ 1(一个选项),但是到剩下的第一个参数(所有选项及其值后面的参数)。例如:myrm -foo -bar = baz thisarg thenthisone theotherother
Olivier Dulac

5

发明轮子的另一个版本...

此功能是(希望)与POSIX兼容的GNU getopt的纯文本外壳替代品。它支持可以接受强制/可选/无参数的短/长选项,并且指定选项的方式几乎与GNU getopt相同,因此转换很简单。

当然,这仍然是放入脚本中的相当大的代码块,但是大约是著名的getopt_long shell函数的一半,在您只想替换现有GNU getopt用法的情况下可能更可取。

这是非常新的代码,所以是YMMV(并且绝对要让我知道它是否由于某种原因实际上与POSIX不兼容-从一开始就希望可移植性,但是我没有有用的POSIX测试环境)。

代码和示例用法如下:

#!/bin/sh
# posix_getopt shell function
# Author: Phil S.
# Version: 1.0
# Created: 2016-07-05
# URL: http://stackoverflow.com/a/37087374/324105

# POSIX-compatible argument quoting and parameter save/restore
# http://www.etalabs.net/sh_tricks.html
# Usage:
# parameters=$(save "$@") # save the original parameters.
# eval "set -- ${parameters}" # restore the saved parameters.
save () {
    local param
    for param; do
        printf %s\\n "$param" \
            | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/"
    done
    printf %s\\n " "
}

# Exit with status $1 after displaying error message $2.
exiterr () {
    printf %s\\n "$2" >&2
    exit $1
}

# POSIX-compatible command line option parsing.
# This function supports long options and optional arguments, and is
# a (largely-compatible) drop-in replacement for GNU getopt.
#
# Instead of:
# opts=$(getopt -o "$shortopts" -l "$longopts" -- "$@")
# eval set -- ${opts}
#
# We instead use:
# opts=$(posix_getopt "$shortopts" "$longopts" "$@")
# eval "set -- ${opts}"
posix_getopt () { # args: "$shortopts" "$longopts" "$@"
    local shortopts longopts \
          arg argtype getopt nonopt opt optchar optword suffix

    shortopts="$1"
    longopts="$2"
    shift 2

    getopt=
    nonopt=
    while [ $# -gt 0 ]; do
        opt=
        arg=
        argtype=
        case "$1" in
            # '--' means don't parse the remaining options
            ( -- ) {
                getopt="${getopt}$(save "$@")"
                shift $#
                break
            };;
            # process short option
            ( -[!-]* ) {         # -x[foo]
                suffix=${1#-?}   # foo
                opt=${1%$suffix} # -x
                optchar=${opt#-} # x
                case "${shortopts}" in
                    ( *${optchar}::* ) { # optional argument
                        argtype=optional
                        arg="${suffix}"
                        shift
                    };;
                    ( *${optchar}:* ) { # required argument
                        argtype=required
                        if [ -n "${suffix}" ]; then
                            arg="${suffix}"
                            shift
                        else
                            case "$2" in
                                ( -* ) exiterr 1 "$1 requires an argument";;
                                ( ?* ) arg="$2"; shift 2;;
                                (  * ) exiterr 1 "$1 requires an argument";;
                            esac
                        fi
                    };;
                    ( *${optchar}* ) { # no argument
                        argtype=none
                        arg=
                        shift
                        # Handle multiple no-argument parameters combined as
                        # -xyz instead of -x -y -z. If we have just shifted
                        # parameter -xyz, we now replace it with -yz (which
                        # will be processed in the next iteration).
                        if [ -n "${suffix}" ]; then
                            eval "set -- $(save "-${suffix}")$(save "$@")"
                        fi
                    };;
                    ( * ) exiterr 1 "Unknown option $1";;
                esac
            };;
            # process long option
            ( --?* ) {            # --xarg[=foo]
                suffix=${1#*=}    # foo (unless there was no =)
                if [ "${suffix}" = "$1" ]; then
                    suffix=
                fi
                opt=${1%=$suffix} # --xarg
                optword=${opt#--} # xarg
                case ",${longopts}," in
                    ( *,${optword}::,* ) { # optional argument
                        argtype=optional
                        arg="${suffix}"
                        shift
                    };;
                    ( *,${optword}:,* ) { # required argument
                        argtype=required
                        if [ -n "${suffix}" ]; then
                            arg="${suffix}"
                            shift
                        else
                            case "$2" in
                                ( -* ) exiterr 1 \
                                       "--${optword} requires an argument";;
                                ( ?* ) arg="$2"; shift 2;;
                                (  * ) exiterr 1 \
                                       "--${optword} requires an argument";;
                            esac
                        fi
                    };;
                    ( *,${optword},* ) { # no argument
                        if [ -n "${suffix}" ]; then
                            exiterr 1 "--${optword} does not take an argument"
                        fi
                        argtype=none
                        arg=
                        shift
                    };;
                    ( * ) exiterr 1 "Unknown option $1";;
                esac
            };;
            # any other parameters starting with -
            ( -* ) exiterr 1 "Unknown option $1";;
            # remember non-option parameters
            ( * ) nonopt="${nonopt}$(save "$1")"; shift;;
        esac

        if [ -n "${opt}" ]; then
            getopt="${getopt}$(save "$opt")"
            case "${argtype}" in
                ( optional|required ) {
                    getopt="${getopt}$(save "$arg")"
                };;
            esac
        fi
    done

    # Generate function output, suitable for:
    # eval "set -- $(posix_getopt ...)"
    printf %s "${getopt}"
    if [ -n "${nonopt}" ]; then
        printf %s "$(save "--")${nonopt}"
    fi
}

用法示例:

# Process command line options
shortopts="hvd:c::s::L:D"
longopts="help,version,directory:,client::,server::,load:,delete"
#opts=$(getopt -o "$shortopts" -l "$longopts" -n "$(basename $0)" -- "$@")
opts=$(posix_getopt "$shortopts" "$longopts" "$@")
if [ $? -eq 0 ]; then
    #eval set -- ${opts}
    eval "set -- ${opts}"
    while [ $# -gt 0 ]; do
        case "$1" in
            ( --                ) shift; break;;
            ( -h|--help         ) help=1; shift; break;;
            ( -v|--version      ) version_help=1; shift; break;;
            ( -d|--directory    ) dir=$2; shift 2;;
            ( -c|--client       ) useclient=1; client=$2; shift 2;;
            ( -s|--server       ) startserver=1; server_name=$2; shift 2;;
            ( -L|--load         ) load=$2; shift 2;;
            ( -D|--delete       ) delete=1; shift;;
        esac
    done
else
    shorthelp=1 # getopt returned (and reported) an error.
fi

4

公认的答案可以很好地指出内置bash的所有缺点getopts。答案以以下结尾:

因此,尽管有可能编写更多代码来解决对长选项缺乏支持的问题,但这需要做更多的工作,并且在一定程度上抵消了使用getopt解析器简化代码的目的。

即使我在原则上同意该声明,但我仍然认为我们所有人都在各种脚本中实现此功能的次数证明,为创建“标准化”,经过测试的解决方案付出一些努力是合理的。

因此,我getopts通过getopts_long纯bash 实现“升级”了内置的bash,而没有任何外部依赖。该功能的用法与内建100%兼容getopts

通过在脚本中包含getopts_long托管在GitHub上),可以简单地实现原始问题的答案:

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

3

我的代表没有足够的评论或投票赞成他的解决方案,但sme的回答对我来说非常有效。我遇到的唯一问题是,参数最终以单引号引起来(因此我将其删除)。

我还添加了一些示例用法和帮助文本。我将在此处包括稍微扩展的版本:

#!/bin/bash

# getopt example
# from: /programming/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

3

在这里,您可以找到几种不同的方法来在bash中进行复杂的选项解析:http : //mywiki.wooledge.org/ComplexOptionParsing

我确实创建了以下代码,我认为这是一个很好的代码,因为这是最少的代码,长短选项都起作用。长选项也可以使用这种方法具有多个参数。

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


2

改进的解决方案:

# 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

2

如果需要很长的命令行选项,也许仅对于getopts部分使用ksh可能更简单,因为在那里可以更轻松地完成。

# 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

+1-请注意,仅限于来自开源AST项目(AT&T研究)的ksh93。
Henk Langeveld

2

我想要一些没有外部依赖项,具有严格的bash支持(-u)的东西,并且甚至在较旧的bash版本上也需要它。这处理各种类型的参数:

  • 短布尔(-h)
  • 简短选项(-i“ image.jpg”)
  • 长布尔(-帮助)
  • 等于选项(--file =“ filename.ext”)
  • 空格选项(--file“ filename.ext”)
  • 糊化的布尔(-hvm)

只需在脚本顶部插入以下内容:

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

并像这样使用它:

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

1

为了保持跨平台兼容性,并避免依赖外部可执行文件,我移植了一些其他语言的代码。

我发现它非常易于使用,这是一个示例:

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下载此文件

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

1

如果您所有的长选项都具有唯一且匹配的首字符作为短选项,那么例如

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

是相同的

./slamm -c 23 -p test -q

您可以 getopts重写$ 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
    .....

感谢mtvee的启发;-)


我在这里没有评估的重要性
user.friendly

1

如果只是这就是您要如何调用脚本

myscript.sh --input1 "ABC" --input2 "PQR" --input2 "XYZ"

那么您可以在getopt和--longoptions的帮助下遵循这种最简单的方法来实现它

试试这个,希望这是有用的

# Read command line options
ARGUMENT_LIST=(
    "input1"
    "input2"
    "input3"
)



# read arguments
opts=$(getopt \
    --longoptions "$(printf "%s:," "${ARGUMENT_LIST[@]}")" \
    --name "$(basename "$0")" \
    --options "" \
    -- "$@"
)


echo $opts

eval set --$opts

while true; do
    case "$1" in
    --input1)  
        shift
        empId=$1
        ;;
    --input2)  
        shift
        fromDate=$1
        ;;
    --input3)  
        shift
        toDate=$1
        ;;
      --)
        shift
        break
        ;;
    esac
    shift
done

0

只要您不希望它们具有参数,getopts就可以解析长选项。

方法如下:

$ 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语句手动处理它。

这将“始终”起作用:

$ 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灵活,并且您必须在案例实例中自己做很多错误检查代码...

但这是一个选择。


但是长选项经常会引起争论。而且,您可以做更多的事情-使它正常工作,即使它有些骇人听闻。最终人们可以说,如果它本身不支持它,然后执行的每一个方式,它是一个黑客的东西,但你仍然可以扩展-太。是的shift是非常有用的,但是当然,如​​果它期望一个参数,则最终可能会导致下一个参数(如果用户未指定)是期望参数的一部分。
Pryftan

是的,这是一个长参数名称(不带参数)的poc,要在两者之间进行区分,您需要某种配置,例如getops。对于班次,您可以随时将其“放回原位”。无论如何,无论是否需要参数,它都必须是可配置的。您甚至可以使用一些魔术,但是随后您将迫使用户使用-表示魔术停止并且位置参数开始,这更糟糕了。
estani

很公平。这是合理的。Tbh我什至不记得我在做什么,而我完全忘记了这个问题。我只隐约记得我是怎么发现它的。干杯。哦,您的想法是+1。您经过了努力,还澄清了所得到的。我尊重确实努力给别人提供想法的人
。– Pryftan

0

内置getopts仅解析短选项(ksh93中除外),但是您仍然可以添加几行脚本来使getopts处理长选项。

这是在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))

这是一个测试:

# Short options test
$ ./foobar_any_getopts.sh -bF "Hello world" -B 6 file1 file2
foo=0 bar=1
barfoo=6
foobar=1 foobar_name=Hello world
files=file1 file2

# Long and short options test
$ ./foobar_any_getopts.sh --bar -F Hello --barfoo 6 file1 file2
foo=0 bar=1
barfoo=6
foobar=1 foobar_name=Hello
files=file1 file2

否则,在最近的Korn Shell程序 ksh93中,getopts自然可以解析长选项,甚至显示手册页。(请参阅http://www.uxora.com/unix/shell-script/20-getopts-with-man-page-and-long-options


0

内置OS X(BSD)getopt不支持长选项,但GNU版本支持:brew install gnu-getopt。然后,类似于:cp /usr/local/Cellar/gnu-getopt/1.1.6/bin/getopt /usr/local/bin/gnu-getopt


By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.