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

Posted

技术标签:

【中文标题】使用 getopts 处理长短命令行选项【英文标题】:Using getopts to process long and short command line options 【发布时间】:2010-09-28 23:28:51 【问题描述】:

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

我知道getopts 可以使用,但就像在 Perl 中一样,我无法使用 shell。

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

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

在上面,这两个命令对我的 shell 意味着同样的事情,但是使用getopts,我无法实现这些?

【问题讨论】:

恕我直言,公认的答案不是最好的。它没有展示如何使用 getopts 来处理“-”和“--”参数,正如@Arvid Requate 所演示的那样,这是可以做到的。我正在使用类似的概念插入另一个答案,但也处理“忘记”为所需参数插入值的用户错误。关键点:可以使 getopts 起作用。如果需要跨平台可移植性,用户应避免使用“getopt”。此外,getopts 是 POSIX 标准的一部分,因此它很可能是可移植的。 How do I parse command line arguments in Bash?的可能重复 【参考方案1】:

getoptgetopts 是不同的野兽,人们似乎对他们的所作所为有点误解。 getoptsbash 的内置命令,用于循环处理命令行选项,并将找到的每个选项和值依次分配给内置变量,以便您进一步处理它们。然而,getopt 是一个外部实用程序,它实际上并没有为您处理您的选项,例如bash getopts、Perl Getopt 模块或 Python optparse/argparse 模块都可以。 getopt 所做的只是规范化传入的选项——即将它们转换为更标准的形式,以便 shell 脚本更容易处理它们。例如,getopt 的应用程序可能会转换以下内容:

myscript -ab infile.txt -ooutfile.txt

进入这个:

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

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

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

为什么使用getopt 而不是getopts?基本原因是只有 GNU getopt 为您提供对长名称命令行选项的支持。1(GNU getopt 是 Linux 上的默认设置。Mac OS X 和 FreeBSD 带有基本的并且不是很有用getopt,但可以安装 GNU 版本;见下文。)

例如,这是一个使用 GNU getopt 的示例,来自我的一个名为 javawrap 的脚本:

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

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

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

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

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

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

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

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

最后,如果您看到的代码只有 set 而不是 eval set,那么它是为 BSD getopt 编写的。您应该将其更改为使用eval set 样式,它适用于getopt 的两个版本,而普通的set 不适用于GNU getopt

1实际上,ksh93 中的 getopts 支持长名称选项,但是这个 shell 不像 bash 那样经常使用。在zsh 中,使用zparseopts 获取此功能。

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

【讨论】:

这很有帮助,当我想向 bash 脚本添加长样式选项时,使用 getopt 检查选项然后在一个非常简单的循环中处理这些选项的想法非常有效。谢谢。 getopt 在Linux 上不是 GNU 实用程序,传统的getopt 最初并非来自BSD,而是来自AT&T Unix。 ksh93 的getopts(也来自 AT&T)支持 GNU 风格的长选项。 @StephaneChazelas -- 已编辑以反映您的 cmets。我仍然更喜欢“GNU getopt”这个词,尽管它用词不当,因为这个版本遵循 GNU 约定并且通常表现得像一个 GNU 程序(例如使用POSIXLY_CORRECT),而“Linux-enhanced getopt”错误地暗示这个版本仅存在于 Linux 上。 它来自 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。 @StephaneChazelas -- FreeBSD 有一条错误消息“构建依赖项:请安装 GNU getopt”,明确指的是getopt util,而不是 getopt(3)。【参考方案2】:

可以考虑三种实现方式:

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

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

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


其他一些答案显示了使用 bash 内置 getopts 模拟长选项的解决方案。该解决方案实际上是一个简短的选项,其字符为“-”。所以你得到“--”作为标志。然后后面的任何内容都变为 OPTARG,然后您使用嵌套的 case 测试 OPTARG。

这很聪明,但也有一些注意事项:

getopts 无法强制执行 opt 规范。如果用户提供了一个无效的选项,它就不能返回错误。在解析 OPTARG 时,您必须自己进行错误检查。 OPTARG 用于长选项名称,当您的长选项本身有参数时,这会使使用变得复杂。您最终不得不自己编写代码作为附加案例。

因此,虽然可以编写更多代码来解决对长选项的支持不足的问题,但这需要更多的工作,并且部分违背了使用 getopt 解析器来简化代码的目的。

【讨论】:

所以。什么是跨平台、便携的解决方案? GNU Getopt 似乎是唯一的选择。在 Mac 上,从 macports 安装 GNU getopt。在 Windows 上,我会使用 Cygwin 安装 GNU getopt。 Apparently, ksh getopts 可以处理长选项。 @Bill +1,尽管在 Mac 上从源代码 (software.frodo.looijaard.name/getopt) 构建 getopt 也相当简单。您还可以使用“getopt -T; echo $?”从脚本中检查系统上安装的 getopt 版本。 @Bill Karwin:“内置的 bash getopts 不支持带有双破折号前缀的长选项名称。”但是可以使 getopts 支持长选项:请参阅下面的 ***.com/a/7680682/915044。【参考方案3】:

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

在current working directory中复制到可执行文件name=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 指出的那样,这个技巧利用了 shell 的非标准行为,它允许将选项参数(即“-f 文件名”中的文件名)连接到选项(如“-ffilename”)。 POSIX 标准规定它们之间必须有一个空格,在“--longoption”的情况下,它将终止选项解析并将所有 longoptions 转换为非选项参数。

【讨论】:

一个问题:!val="$!OPTIND中的语义是什么? @TomRoche 是间接替换:unix.stackexchange.com/a/41293/84316 @ecbrodie:这是因为实际上已经处理了两个参数,而不仅仅是一个。第一个参数是单词“loglevel”,下一个是 that 参数的参数。同时,getopts 只会自动将OPTIND 增加 1,但在我们的例子中,我们需要它增加 2,所以我们手动将其增加 1,然后让 getopts 再次为我们自动增加 1。跨度> 由于我们在这里进入 bash 均衡:算术表达式中允许使用裸变量名,不需要 $OPTIND=$(( $OPTIND + 1 )) 可以只是 OPTIND=$(( OPTIND + 1 ))。更有趣的是,您甚至可以在算术表达式中分配和增加变量,因此可以将其进一步缩写为 : $(( ++OPTIND )),甚至是 (( ++OPTIND )),考虑到 ++OPTIND 将始终为正数,因此它不会使用 -e 选项启动 shell 运行。 :-) gnu.org/software/bash/manual/html_node/Shell-Arithmetic.html 为什么--very-bad不发出警告?【参考方案4】:

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

有(或曾经有)一个外部程序 getopt 可以重新组织一组选项,以便更容易解析。您也可以调整该设计以处理长选项。示例用法:

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

# Process remaining non-option arguments
...

您可以通过getoptlong 命令使用类似的方案。

请注意,外部getopt 程序的根本弱点是难以处理带有空格的参数,并且难以准确地保留这些空格。这就是为什么内置 getopts 更优越的原因,尽管它只处理单字母选项这一事实受到限制。

【讨论】:

getopt,除了 GNU 版本(它有不同的调用约定),从根本上被破坏了。不要使用它。请改用 **getopts bash-hackers.org/wiki/doku.php/howto/getopts_tutorial @hendry - 来自您自己的链接:“请注意,getopts 无法解析 GNU 样式的长选项 (--myoption) 或 XF86 样式的长选项 (-myoption)!” Jonathan -- 您应该重写示例以使用带引号的eval set(请参阅下面的答案),以便它也可以与 GNU getopt(Linux 上的默认设置)一起正常工作并正确处理空格。 @UrbanVagabond:我不知道我为什么要这样做。问题被标记为 Unix,而不是 Linux。我故意展示了传统机制,它在参数中存在空格等问题。如果您愿意,您可以演示现代 Linux 特定版本,您的答案就是这样做的。 (我注意到,帕西姆,你对$1+"$@" 的使用很古怪,并且与现代 shell 中的必要内容不一致,特别是与你在 Linux 上找到的任何 shell 不一致。有关该符号的讨论,请参阅Using $1:+"$@" in /bin/sh。)跨度> @hendry 评论的链接(此答案的第一条评论)已移至 wiki.bash-hackers.org/howto/getopts_tutorial【参考方案5】:

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

这是可移植的原生 POSIX shell - 不需要外部程序或 bashism。

本指南将长选项实现为- 选项的参数,因此--alphagetopts 视为-alpha--bravo=foo 被视为- 与@987654334 @。真正的论点可以通过一个简单的替换来获得:$OPTARG#*=

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

因为它使用getopts builtin,所以该解决方案支持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://***.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

When the option is a dash (-), it is a long option. getopts 会将实际的 long 选项解析为 $OPTARG,例如--bravo=foo 最初设置 OPT='-'OPTARG='bravo=foo'if 节将$OPT 设置为$OPTARG 在第一个等号之前的内容(在我们的示例中为bravo),然后从$OPTARG 的开头删除它(在此步骤中产生=foo,或者如果没有=,则为空字符串)。最后,我们去掉参数的前导=。此时,$OPT 是短选项(一个字符)或长选项(2+ 个字符)。

case 然后匹配短选项或长选项。对于短选项,getopts 会自动抱怨选项和缺少参数,因此我们必须使用 needs_arg 函数手动复制这些选项,当 $OPTARG 为空时该函数会致命地退出。 ??* 条件将匹配任何剩余的长选项(? 匹配单个字符,* 匹配零个或多个字符,因此??* 匹配 2+ 个字符),允许我们在退出前发出“非法选项”错误.


小错误:如果有人给出了无效的单字符长选项(而且它也不是短选项),这将退出并出现错误但没有消息。这是因为此实现假定它是一个简短的选项。您可以在 case 之前的长选项节中使用一个额外的变量来跟踪它,然后在最后的情况下对其进行测试,但我认为这太过分了。

大写变量名: 通常,建议保留全大写变量以供系统使用。我将$OPT 保持为全大写以使其与$OPTARG 保持一致,但这确实违反了该约定。我认为它很合适,因为这是系统应该做的事情,而且它应该是安全的,因为没有使用这种变量的标准 (afaik)。)

抱怨长选项的意外参数: 使用翻转测试模仿 needs_arg 来抱怨不期望的参数:

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

要接受带有空格分隔参数的长选项: 您可以使用 eval "ARG_B=\"\$$OPTIND\""(或 bash 的 indirect expansion、ARG_B="$!OPTIND")拉入下一个参数,然后将 $OPTIND 递增为在older version of this answer 中注明,但它不可靠; getopts 可以在参数超出其范围的假设下提前终止,并且某些实现不适合手动操作 $OPTIND

【讨论】:

非常好的独立解决方案。问题一:既然letter-c不需要论证,那么使用letter-c)还不够吗? * 似乎是多余的。 @Arne 位置参数是糟糕的用户体验;它们很难理解,可选参数非常混乱。 getopts 停在第一个位置参数,因为它不是为处理它们而设计的。这允许子命令带有自己的参数,例如git diff --color,所以我将command --foo=moo bar --baz waz 解释为--foo 作为command 的参数,--baz waz 作为bar 子命令的参数(带有选项)。这可以通过上面的代码来完成。我拒绝--bravo -blah,因为--bravo 需要一个参数,并且不清楚-blah 不是另一种选择。 我不同意 UX:位置参数是有用且简单的,只要你限制它们的数量(最多 2 或 1 加上 N-of-the-same-type)。应该可以在它们之间穿插关键字参数,因为用户可以逐步构建命令(即 ls a b c -la)。 @AdamKatz:我用这个写了一篇小文章:draketo.de/english/free-software/shell-argument-parsing — 包括重复阅读剩余参数以捕获尾随选项。 @ArneBabenhauserheide:我已更新此答案以支持以空格分隔的参数。因为它在 POSIX shell 中需要eval,所以它列在答案的其余部分下方。【参考方案6】:

这是一个实际使用带有长选项的 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

【讨论】:

您应该重写示例以使用带引号的eval set(请参阅下面的答案),以便它也可以与 GNU getopt(Linux 上的默认设置)一起正常工作并正确处理空格。 这是使用getopt,而问题是关于getopts (--(-*(* 是有效模式吗?它们与---** 有何不同? @Maëlan – 前导左括号是可选的,因此(--)case 节中的--) 相同。看到可选前导括号的不均匀缩进和不一致使用很奇怪,但答案的当前代码对我来说似乎有效。【参考方案7】:

getopts 与短/长选项和参数一起使用


适用于所有组合,例如:

foobar -f --bar foobar --foo -b foobar -bf --bar --foobar foobar -fbFBAshorty --bar -FB --arguments=longhorn foobar -fA "text shorty" -B --arguments="text longhorn" bash foobar -F --barfoo sh foobar -B --foobar - ... bash ./foobar -F --bar

本示例的一些声明

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

Usage 函数的外观

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 – 是的,我正在使用 eval 方法来处理长选项的间隔参数,发现它在某些 shell 上不可靠(尽管我希望它可以与 bash 一起使用,在这种情况下你不必使用eval)。请参阅my answer,了解如何使用= 接受长选项参数以及我注意到的使用空间的尝试。我的解决方案不拨打外部电话,而这个解决方案使用cut 几次。 我尝试让 $lARG 保留 $OPTARG,但它从来没有用过。我似乎无法为 --arguments 提供任何实际选项。【参考方案8】:

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

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

这是一个使用 shFlag 的简单 Hello, world!

#!/bin/sh

# source shflags from current directory
. ./shflags

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

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

# say hello
echo "Hello, $FLAGS_name!"

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

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

对于其余部分,您必须使用短选项:

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

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

【讨论】:

这太棒了,但不幸的是我的 getopt (OS X) 不支持参数中的空格:/ 想知道是否有替代方案。 @AlastairStuart -- 在 OS X 上确实有一个替代方案。使用 MacPorts 安装 GNU getopt(它通常会安装到 /opt/local/bin/getopt)。 @UrbanVagabond - 不幸的是,安装非系统默认工具对于足够便携的工具来说是不可接受的要求。 @AlastairStuart – 请参阅my answer 了解使用内置 getopts 而不是 GNU getopt 的便携式解决方案。它与基本的 getopts 用法相同,但对长选项进行了额外的迭代。【参考方案9】:

另一种方式...

# 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

【讨论】:

这不是在每个$args 重新分配中都需要一个空格吗?这甚至可以在没有 bashism 的情况下完成,但是此代码将丢失选项和参数中的空格(我认为 $delim 技巧不会起作用)。如果您足够小心,仅在第一次迭代时将其清空,则可以改为运行 set inside for 循环。 Here is a safer version 没有 bashisms。【参考方案10】:

我是这样解决的:

# 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 太混乱了。

【讨论】:

这似乎对我有用,我不知道这个方法有什么问题,但看起来很简单,所以肯定有其他人没有使用它的原因。 @Billy 是的,这很简单,因为我不使用任何脚本来管理我的参数等。基本上我将参数字符串 ($@) 转换为数组并循环遍历它。在循环中,当前值是键,下一个值是值。就这么简单。 @Theodore 很高兴这对您有所帮助!这对我来说也是一种痛苦。如果您有兴趣,可以在此处查看它的示例:raw.github.com/rafaelrinaldi/swf-to-html/master/swf-to-html.sh 绝对是我见过的最简单的方法。我对其进行了一些更改,例如使用 i=$(( $i + 1 )) 而不是 expr,但这个概念是密封的。 你一点也不笨,但你可能缺少一个功能:getopt(s) 可以识别混合的选项(例如:-ltr-lt -r 以及 -l -t -r ) .它还提供了一些错误处理,以及在选项处理完成后将处理过的参数移走的简单方法。【参考方案11】:

如果你不想要 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 功能以及长选项,这是一种简单的方法。

我还将这个 sn-p 放在 gist 中。

【讨论】:

这似乎一次只能使用一个长选项,但它满足了我的需要。谢谢! 在特殊情况下--) 似乎缺少shift ;。目前-- 将保留为第一个非选项参数。 我认为这实际上是更好的答案,尽管正如 dgw 指出的那样,-- 选项需要一个shift。我说这更好,因为替代方案是getoptgetopts_long 的平台相关版本,或者您必须强制仅在命令开头使用短选项(即 - 您使用getopts 然后处理长之后的选项),而这给出了任何顺序和完全控制。 这个答案让我想知道为什么我们有几十个答案的线程来完成这项工作,而这些工作只需这个绝对清晰和直接的解决方案就可以完成,如果除了证明自己之外,还有任何理由使用十亿个 getopt(s) 用例。【参考方案12】:

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

还有一个getopts_long写成一个shell函数。

【讨论】:

getopt 于 1993 年包含在 FreeBSD 版本 1.0 中,从那时起一直是 FreeBSD 的一部分。因此,它被 FreeBSD 4.x 采用并包含在 Apple 的 Darwin 项目中。从 OS X 10.6.8 开始,Apple 包含的手册页与 FreeBSD 手册页完全相同。所以是的,它包含在 OS X 和除 Linux 之外的其他操作系统中。错误信息的答案为-1。【参考方案13】:
#!/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

【讨论】:

一个解释会很好。第一个脚本仅接受短选项,而第二个脚本在其长选项参数解析中存在错误;它的变量应该是 "$1:0:1" 对于参数 #1,索引 0 处的子字符串,长度 1。这不允许混合短选项和长选项。【参考方案14】:

ksh93 中,getopts 确实支持长名称...

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

或者我发现的教程是这样说的。试试看。

【讨论】:

这是 ksh93 的内置 getopts。除了这种语法,它还有一个更复杂的语法,也允许长选项没有短等价物,等等。 一个合理的答案。 OP 没有指定 WHAT 外壳。【参考方案15】:

发明另一个版本的***...

这个函数是一个(希望是)与 POSIX 兼容的普通 bourne shell 替代 GNU getopt。它支持短/长选项,可以接受强制/可选/无参数,并且指定选项的方式与GNU getopt几乎相同,因此转换很简单。

当然,这仍然是一个相当大的代码块放入脚本,但它大约是众所周知的 getopt_long shell 函数的一半,并且在您只想替换现有的 GNU getopt 使用的情况下可能更可取.

这是相当新的代码,所以 YMMV(如果出于任何原因这实际上与 POSIX 不兼容,请务必告诉我——可移植性从一开始就是目的,但我没有有用的 POSIX 测试环境)。

代码和示例用法如下:

#!/bin/sh
# posix_getopt shell function
# Author: Phil S.
# Version: 1.0
# Created: 2016-07-05
# URL: http://***.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

【讨论】:

我很难弄清楚eval set 究竟做了什么。安全吗? 如果参数(在所有正常的 shell 替换和扩展过程之后)产生一个安全的表达式是安全的。在这种情况下,我们将生成一个 set -- ... 命令,dash 的手册页解释如下:“set 命令的第三个用途是将 shell 的位置参数的值设置为指定的 args。要更改位置参数不改变任何选项,使用“--”作为第一个参数来设置。” (POSIX shell 文档也这么说,但不太清楚。) 谢谢。我还没有完全理解两件事。 1. 为什么需要“eval”?我的研究仅表明您在使用 GNU getopt 时需要它。但为什么? 2. 我们可以假设“参数产生了一个安全的表达式”吗? eval 是必需的,因为我们(GNU getopt 和 posix_getopt)都通过生成 shell 引用的值 字符串来支持包含特殊字符的选项,其中一次是 @987654327 @uated,将再次产生原始值(现在被处理为准备处理的标准化格式)。例如,--foo="hello, world." 中的空间将是一个问题,否则。 posix_getopt 的返回值必须以演示的方式通过 eval 才能工作。 关于 (2),我只能说 确信生成了一个安全的表达式(毕竟我写这个是为了我自己的使用),但是我不确定我能做的不仅仅是鼓励您阅读/理解代码以说服自己相信同样的事情。要了解发生了什么,您可以尝试将各种“不安全”输入传递给它,然后用echo "set -- $opts" 代替echo "set -- $opts" 来查看评估什么。【参考方案16】:

我只是不时编写 shell 脚本并且没有实践,因此感谢任何反馈。

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

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

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

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

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

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

    如果用户具有长/等号格式 (--opt=),则 = 后的空格会触发错误,因为未提供参数。 如果用户有长/空格参数 (--opt ),如果后面没有参数(命令结束)或参数以破折号开头,则此脚本会导致失败

如果您是从这方面开始的,“--opt=value”和“--opt value”格式之间有一个有趣的区别。使用等号,命令行参数被视为“opt=value”,处理字符串解析的工作是在“=”处分隔。相反,使用“--opt value”,参数的名称是“opt”,我们面临着获取命令行中提供的下一个值的挑战。这就是@Arvid Requate 使用间接引用 $!OPTIND 的地方。我仍然不明白,好吧,BashFAQ 中的 cmets 似乎警告不要这种风格(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
## https://***.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"

【讨论】:

OPTIND=$(( $OPTIND + 1 )) :每当您“吞噬” OPTIND 的参数时都需要它(例如:当使用 --toc value 时:值在参数编号 $OPTIND 中。一旦您检索到它的 toc 值,你应该告诉 getopts 下一个要解析的参数不是值,而是它后面的那个(因此:OPTIND=$(( $OPTIND + 1 )) 。和你的脚本(以及你引用的脚本)在完成后丢失了:shift $(( $OPTIND -1 )) (由于 getopts 在将参数 1 解析为 OPTIND-1 后退出,您需要将它们移出,因此 $@ 现在是任何剩余的“非选项”参数 哦,当你改变自己时,你“改变”了getopts下面的参数,所以OPTIND总是指向正确的东西......但我觉得它很混乱。我相信(现在无法测试您的脚本)在 getopts while 循环之后您仍然需要 shift $(( $OPTIND - 1 )) ,因此 $1 现在不指向原始 $1 (一个选项)但是到剩余参数中的第一个(在所有选项及其值之后出现的参数)。例如:myrm -foo -bar=baz thisarg thenthisone thenanother【参考方案17】:

已接受的答案很好地指出了 bash 内置 getopts 的所有缺点。答案是:

因此,虽然可以编写更多代码来解决对长选项的支持不足的问题,但这需要更多的工作,并且部分违背了使用 getopt 解析器来简化代码的目的。

尽管我原则上同意该声明,但我认为我们在各种脚本中实现此功能的次数证明了为创建“标准化”、经过良好测试的解决方案付出一些努力是合理的。

因此,我通过在纯 bash 中实现 getopts_long 来“升级”内置于 getopts 的 bash,没有外部依赖项。该功能的使用与内置getopts100%兼容。

通过在脚本中包含getopts_long(即hosted on 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

【讨论】:

【参考方案18】:

我还没有足够的代表来评论或投票支持他的解决方案,但sme's answer 对我来说非常有效。我遇到的唯一问题是参数最终用单引号括起来(所以我把它们去掉了)。

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

#!/bin/bash

# getopt example
# from: https://***.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

【讨论】:

只需使用 -u 作为 getopt 的参数,就可以得到 unquotes 值。【参考方案19】:

您可以在这里找到几种在 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

【讨论】:

【参考方案20】:

我已经在这个主题上工作了很长时间......并制作了我自己的库,您需要在主脚本中获取该库。 有关示例,请参见 libopt4shell 和 cd2mpc。 希望对您有所帮助!

【讨论】:

【参考方案21】:

改进的解决方案:

# 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

【讨论】:

【参考方案22】:

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

# 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 -- 请注意,这仅限于 ksh93 - 来自开源 AST 项目(AT&T Research)。【参考方案23】:

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

短布尔值 (-h) 短选项 (-i "image.jpg") 长布尔值 (--help) 等于选项(--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 $*

【讨论】:

【参考方案24】:

如果这就是你想要调用脚本的方式

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

【讨论】:

如何添加空头选项?【参考方案25】:

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 灵活,而且您必须在案例实例中自己执行大部分错误检查代码...

但这是一种选择。

【讨论】:

但是长选项通常确实需要参数。你可以用 - 做更多的事情来让它工作,即使它有点像黑客。最后,有人可能会争辩说,如果它本身不支持它,那么实现它的每一种方式都是一种 hack,但是你也可以扩展 - 。是的,shift 非常有用,但当然,如果它需要一个参数,它最终可能会导致下一个参数(如果用户没有指定)是预期参数的一部分。 是的,这是一个不带参数的长参数名称的 poc,要区分两者,您需要某种配置,例如 getops。关于班次,你总是可以用 set 把它“放回去”。在任何情况下,如果期望或不期望参数,它必须是可配置的。您甚至可以为此使用一些魔法,但随后您将强制用户使用 -- 来表示魔法停止和位置参数开始,这更糟糕。 很公平。这非常合理。 Tbh 我什至不记得我在做什么,我完全忘记了这个问题本身。我只是模糊地记得我是怎么找到它的。干杯。哦,对这个想法+1。你经历了努力,你也澄清了你在做什么。我尊重那些努力为他人提供想法等的人。【参考方案26】:

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

我觉得它很容易使用,这里有一个例子:

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

【讨论】:

【参考方案27】:

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

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

是一样的

./slamm -c 23 -p test -q

您可以使用这个 before 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 的启发 ;-)

【讨论】:

这里没搞懂eval的意义【参考方案28】:

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

【讨论】:

【参考方案29】:

内置 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

【讨论】:

【参考方案30】:

一个简单的DIY,只得到长名称的args:

用途:

$ ./test-args.sh --a1 a1 --a2 "a 2" --a3 --a4= --a5=a5 --a6="a 6"
a1 = "a1"
a2 = "a 2"
a3 = "TRUE"
a4 = ""
a5 = "a5"
a6 = "a 6"
a7 = ""

脚本:

#!/bin/bash

function main() 
    ARGS=`getArgs "$@"`

    a1=`echo "$ARGS" | getNamedArg a1`
    a2=`echo "$ARGS" | getNamedArg a2`
    a3=`echo "$ARGS" | getNamedArg a3`
    a4=`echo "$ARGS" | getNamedArg a4`
    a5=`echo "$ARGS" | getNamedArg a5`
    a6=`echo "$ARGS" | getNamedArg a6`
    a7=`echo "$ARGS" | getNamedArg a7`

    echo "a1 = \"$a1\""
    echo "a2 = \"$a2\""
    echo "a3 = \"$a3\""
    echo "a4 = \"$a4\""
    echo "a5 = \"$a5\""
    echo "a6 = \"$a6\""
    echo "a7 = \"$a7\""

    exit 0



function getArgs() 
    for arg in "$@"; do
        echo "$arg"
    done



function getNamedArg() 
    ARG_NAME=$1

    sed --regexp-extended --quiet --expression="
        s/^--$ARG_NAME=(.*)\$/\1/p  # Get arguments in format '--arg=value': [s]ubstitute '--arg=value' by 'value', and [p]rint
        /^--$ARG_NAME\$/           # Get arguments in format '--arg value' ou '--arg'
            n                       # - [n]ext, because in this format, if value exists, it will be the next argument
            /^--/! p                # - If next doesn't starts with '--', it is the value of the actual argument
            /^--/                  # - If next do starts with '--', it is the next argument and the actual argument is a boolean one
                # Then just repla[c]ed by TRUE
                c TRUE
            
        
    "



main "$@"

【讨论】:

以上是关于使用 getopts 处理长短命令行选项的主要内容,如果未能解决你的问题,请参考以下文章

处理用户输入与显示数据------------(getopt命令)

LINUX getopt处理命令行参数

在 C 中处理参数前后的命令行选项

使用多个参数解析命令行选项 [getopt?]

自学Linux Shell13.2-选项处理(主要getoptgetopts命令)

处理用户输入与显示数据------------(更高级的getopts命令标准化选项)