ChatGPT解决这个技术问题 Extra ChatGPT

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

我希望使用我的 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 标准的一部分,因此它很可能是可移植的。

v
vaeVictis

getoptgetopts 是不同的野兽,人们似乎对他们的所作所为有点误解。 getoptsbash 的内置命令,用于循环处理命令行选项,并将找到的每个选项和值依次分配给内置变量,以便您进一步处理它们。但是,getopt 是一个外部实用程序,它实际上不会像 bash getopts、Perl Getopt 模块或 Python {8 这样的方式为您处理选项 }/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 或 {6 } 样式,以任何顺序混合选项和非选项等。如果发现无法识别或模棱两可的选项,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 检查选项然后在一个非常简单的循环中处理这些选项的想法非常有效。谢谢。
Linux 上的 getopt不是 GNU 实用程序,传统的 getopt 最初并非来自 BSD,而是来自 AT&T Unix。 ksh93 的 getopts(也来自 AT&T)支持 GNU 风格的长选项。
@StephaneChazelas - 编辑以反映您的评论。我仍然更喜欢“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。
我可以问一下为什么我们需要“$TEMP”周围的引号?被getopt解析后,不会有分号。在 set -- 之后,破折号和引号将被视为参数。我已经测试了一些组合。但是我仍然找不到 eval set -- $TEMP 的行为与 eval set -- "$TEMP" 不同的示例。感谢您的出色回答。
j
jones77

可以考虑三种实现方式:

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 支持长选项:参见下面的 stackoverflow.com/a/7680682/915044
T
TomRoche

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 不发出警告?
J
Jonathan Leffler

内置的 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)!”
乔纳森——您应该重写示例以使用带引号的 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
A
Adam Katz

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

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

本指南将长选项实现为 - 选项的参数,因此 getopts--alpha 视为具有参数 alpha-,而将 --bravo=foo 视为具有参数 bravo=foo-。真正的参数是通过 shell 参数扩展获取的,更新 $OPT$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://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

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

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

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

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

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

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

要接受带有空格分隔参数的长选项: 您可以使用 eval "ARG_B=\"\$$OPTIND\""(或使用 bash 的 indirect expansionARG_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 作为参数(带选项)到 {7 } 子命令。这可以通过上面的代码来完成。我拒绝 --bravo -blah,因为 --bravo 需要一个参数,并且不清楚 -blah 不是另一个选项。
我不同意 UX:位置参数是有用且简单的,只要你限制它们的数量(最多 2 或 1 加上 N-of-the-same-type)。应该可以在它们之间穿插关键字参数,因为用户可以逐步构建命令(即 ls abc -la)。
@AdamKatz:我用这个写了一篇小文章:draketo.de/english/free-software/shell-argument-parsing — 包括重复阅读剩余参数以捕获尾随选项。
@ArneBabenhauserheide:我已更新此答案以支持以空格分隔的参数。因为它在 POSIX shell 中需要 eval,所以它列在答案的其余部分下方。
F
Florian Feldhaus

这是一个实际使用带有长选项的 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 节中的 --) 相同。看到可选前导括号的不均匀缩进和不一致使用很奇怪,但答案的当前代码对我来说似乎有效。
W
Will

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

适用于所有组合,例如:

foobar -f --bar

foobar --foo -b

foobar -bf --bar --foobar

foobar -fbFBAshorty --bar -FB --arguments=longhorn

foobar -fA "文字矮" -B --arguments="文字长角"

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 "

带有长/短标志以及长参数的 getop

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 一起使用,在这种情况下你不必使用 {2 })。请参阅 my answer,了解如何使用 = 接受长选项参数以及我注意到的使用空间的尝试。我的解决方案不会拨打外部电话,而这个解决方案使用 cut 几次。
我尝试让 $lARG 保留 $OPTARG,但它从来没有用过。我似乎无法为 --arguments 提供任何实际选项。
k
k0pernikus

看看 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!

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

$ ./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 用法相同,但对长选项有额外的迭代。
m
mtvee

另一种方式...

# 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 技巧不会起作用)。如果您足够小心,仅在第一次迭代时将其清空,则可以改为在 for 循环中运行 set insideHere is a safer version 没有 bashisms。
B
BenMorel

我是这样解决的:

# 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 )。它还提供了一些错误处理,以及在选项处理完成后将处理过的参数移走的简单方法。
j
jakesandlund

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

我还将此代码段放在 gist 中。


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

内置的 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 在这个错误信息的答案上。
3
3ED
#!/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,其变量应为 "${1:0:1}",索引为 0 的子字符串,长度为 1。这不允许混合短选项和长选项。
t
tripleee

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

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

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


这是 ksh93 的内置 getopts。除了这种语法之外,它还有一个更复杂的语法,它还允许长选项而没有短等价物,等等。
一个合理的答案。 OP没有指定什么外壳。
p
phils

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

这个函数是 GNU getopt 的(希望是)与 POSIX 兼容的普通 bourne shell 替代品。它支持可以接受强制/可选/无参数的短/长选项,并且指定选项的方式几乎与 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

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

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

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

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

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

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

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

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

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

如果您从这个开始,“--opt=value”和“--opt value”格式之间存在一个有趣的区别。使用等号,命令行参数被视为“opt=value”,处理字符串解析的工作是在“=”处分隔。相反,使用“--opt value”,参数的名称是“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
## https://stackoverflow.com/questions/402377/using-getopts-in-bash-shell-script-to-get-long-and-short-command-line-options
## by @Arvid Requate, and http://mwiki.wooledge.org/BashFAQ/035

# What I don't understand yet: 
# In @Arvid REquate's answer, we have 
# val="${!OPTIND}"; OPTIND=$(( $OPTIND + 1 ))
# this works, but I don't understand it!


die() {
    printf '%s\n' "$1" >&2
    exit 1
}

printparse(){
    if [ ${VERBOSE} -gt 0 ]; then
        printf 'Parse: %s%s%s\n' "$1" "$2" "$3" >&2;
    fi
}

showme(){
    if [ ${VERBOSE} -gt 0 ]; then
        printf 'VERBOSE: %s\n' "$1" >&2;
    fi
}


VERBOSE=0
loglevel=0
toc="TRUE"

optspec=":vhl:t:-:"
while getopts "$optspec" OPTCHAR; do

    showme "OPTARG:  ${OPTARG[*]}"
    showme "OPTIND:  ${OPTIND[*]}"
    case "${OPTCHAR}" in
        -)
            case "${OPTARG}" in
                loglevel) #argument has no equal sign
                    opt=${OPTARG}
                    val="${!OPTIND}"
                    ## check value. If negative, assume user forgot value
                    showme "OPTIND is {$OPTIND} {!OPTIND} has value \"${!OPTIND}\""
                    if [[ "$val" == -* ]]; then
                        die "ERROR: $opt value must not have dash at beginning"
                    fi
                    ## OPTIND=$(( $OPTIND + 1 )) # CAUTION! no effect?
                    printparse "--${OPTARG}" "  " "${val}"
                    loglevel="${val}"
                    shift
                    ;;
                loglevel=*) #argument has equal sign
                    opt=${OPTARG%=*}
                    val=${OPTARG#*=}
                    if [ "${OPTARG#*=}" ]; then
                        printparse "--${opt}" "=" "${val}"
                        loglevel="${val}"
                        ## shift CAUTION don't shift this, fails othewise
                    else
                        die "ERROR: $opt value must be supplied"
                    fi
                    ;;
                toc) #argument has no equal sign
                    opt=${OPTARG}
                    val="${!OPTIND}"
                    ## check value. If negative, assume user forgot value
                    showme "OPTIND is {$OPTIND} {!OPTIND} has value \"${!OPTIND}\""
                    if [[ "$val" == -* ]]; then
                        die "ERROR: $opt value must not have dash at beginning"
                    fi
                    ## OPTIND=$(( $OPTIND + 1 )) #??
                    printparse "--${opt}" " " "${val}"
                    toc="${val}"
                    shift
                    ;;
                toc=*) #argument has equal sign
                    opt=${OPTARG%=*}
                    val=${OPTARG#*=}
                    if [ "${OPTARG#*=}" ]; then
                        toc=${val}
                        printparse "--$opt" " -> " "$toc"
                        ##shift ## NO! dont shift this
                    else
                        die "ERROR: value for $opt undefined"
                    fi
                    ;;

                help)
                    echo "usage: $0 [-v] [--loglevel[=]<value>] [--toc[=]<TRUE,FALSE>]" >&2
                    exit 2
                    ;;
                *)
                    if [ "$OPTERR" = 1 ] && [ "${optspec:0:1}" != ":" ]; then
                        echo "Unknown option --${OPTARG}" >&2
                    fi
                    ;;
            esac;;
        h|-\?|--help)
            ## must rewrite this for all of the arguments
            echo "usage: $0 [-v] [--loglevel[=]<value>] [--toc[=]<TRUE,FALSE>]" >&2
            exit 2
            ;;
        l)
            loglevel=${OPTARG}
            printparse "-l" " "  "${loglevel}"
            ;;
        t)
            toc=${OPTARG}
            ;;
        v)
            VERBOSE=1
            ;;

        *)
            if [ "$OPTERR" != 1 ] || [ "${optspec:0:1}" = ":" ]; then
                echo "Non-option argument: '-${OPTARG}'" >&2
            fi
            ;;
    esac
done



echo "
After Parsing values
"
echo "loglevel  $loglevel" 
echo "toc  $toc"

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

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

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

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

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

通过在脚本中包含 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

C
Community

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

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

#!/bin/bash

# getopt example
# from: https://stackoverflow.com/questions/402377/using-getopts-in-bash-shell-script-to-get-long-and-short-command-line-options
HELP_TEXT=\
"   USAGE:\n
    Accepts - and -- flags, can specify options that require a value, and can be in any order. A double-hyphen (--) will stop processing options.\n\n

    Accepts the following forms:\n\n

    getopt-example.sh -a -b -c value-for-c some-arg\n
    getopt-example.sh -c value-for-c -a -b some-arg\n
    getopt-example.sh -abc some-arg\n
    getopt-example.sh --along --blong --clong value-for-c -a -b -c some-arg\n
    getopt-example.sh some-arg --clong value-for-c\n
    getopt-example.sh
"

aflag=false
bflag=false
cargument=""

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

set -- $options

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

# to remove the single quotes around arguments, pipe the output into:
# | sed -e "s/^'\\|'$//g"  (just leading/trailing) or | sed -e "s/'//g"  (all)

echo aflag=${aflag}
echo bflag=${bflag}
echo cargument=${cargument}

while [ $# -gt 0 ]
do
    echo arg=$1
    shift

    if [[ $aflag == true ]]; then
        echo a is true
    fi

done

只需使用 -u 作为 getopt 的参数,您就会得到 unquotes 值。
u
user3573558

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

l
liealgebra

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


R
Rebecca Scott

改进的解决方案:

# 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

e
efstathiou_e

也许使用 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)。
H
Heath Dutton

我想要一些没有外部依赖的东西,具有严格的 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 $*

A
Ashish Shetkar

如果只是这就是您要调用脚本的方式

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

如何添加空头期权?
e
estani

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。你经历了努力,你也澄清了你在做什么。我尊重那些努力为他人提供想法等的人。
O
Orwellophile

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

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

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

B
BenMorel

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

./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 的启发 ;-)


我在这里不明白 eval 的意义
t
tripleee

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


w
wprl

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


l
lmcarreiro

一个简单的 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 "$@"