关于bash:使用getopts处理长和短命令行选项

Using getopts to process long and short command line options

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

我知道可以使用getopts,但和Perl一样,我无法对shell执行相同的操作。

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

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

在上面,这两个命令对我的shell来说都是相同的,但是使用getopts,我还不能实现这些命令吗?


getoptgetopts是不同的动物,人们似乎对他们的所作所为有点误解。getopts是一个内置命令,用于bash在一个循环中处理命令行选项,并将每个找到的选项和值依次分配给内置变量,以便您进一步处理它们。但是,getopt是一个外部实用程序,它实际上并不像bash getopts、perl getopt模块或python optparse/argparse模块那样处理您的选项。getopt所做的就是将传入的选项规范化,即将它们转换为更标准的形式,这样shell脚本就更容易处理它们。例如,getopt的应用程序可以转换以下内容:好的。

1
myscript -ab infile.txt -ooutfile.txt

进入这个:好的。

1
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:好的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# NOTE: This requires GNU getopt.  On Mac OS X and FreeBSD, you have to install this
# separately; see below.
TEMP=`getopt -o vdm: --long verbose,debug,memory:,debugfile:,minheap:,maxheap: \
             -n 'javawrap' --"$@"`

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

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

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

这允许您指定诸如--verbose -dm4096 --minh=20 --maxhe 40 --debugfi="/Users/John Johnson/debug.txt"或类似的选项。调用getopt的效果是将选项规范化为--verbose -d -m 4096 --minheap 20 --maxheap 40 --debugfile"/Users/John Johnson/debug.txt"以便您更容易地处理它们。关于"$1""$2"的引用很重要,因为它可以确保有空格的论点得到正确处理。好的。

如果删除前9行(通过eval set行的所有内容),代码仍然有效!但是,您的代码在它接受的选项种类上会更加挑剔:特别是,您必须以上面描述的"规范"形式指定所有选项。但是,使用getopt时,您可以对单个字母选项进行分组,使用较短且不含糊的长选项形式,使用--file foo.txt--file=foo.txt样式,使用-m 4096-m4096样式,混合选项和任意顺序的非选项等。getopt如果无法识别或无法识别,也会输出错误消息。找到了巨大的选项。好的。

注:实际上有两个完全不同的版本的getopt,基本getopt和gnu getopt,具有不同的特性和不同的调用约定。cx1〔4〕确实做得对。上述代码在基本getopt中不起作用。gnu getopt默认安装在Linux上,但在Mac OS X和freebsd上需要单独安装。在Mac OS X上,安装MacPorts(http://www.macports.org),然后执行sudo port install getopt安装gnu getopt(通常安装到/opt/local/bin中),并确保/opt/local/bin位于/usr/bin之前的shell路径中。在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 builtin getopts。这不支持带双破折号前缀的长选项名。它只支持单字符选项。

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

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

其他一些答案显示了使用bash内置的getopts模拟长选项的解决方案。该解决方案实际上是一个短选项,其字符为"-"。所以你得到"—"作为旗子。然后,接下来的任何内容都将成为optarg,您将使用嵌套的case来测试optarg。

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

  • getopts不能执行opt规范,如果用户提供的选项无效,则不能返回错误。在分析optarg时,必须进行自己的错误检查。
  • optarg用于long选项名,当long选项本身有参数时,这会使用法复杂化。最后你不得不自己编写代码作为一个附加的案例。

因此,尽管可以编写更多的代码来解决对长选项缺乏支持的问题,但这要花费更多的工作,并且部分地破坏了使用getopt解析器简化代码的目的。


bash builtin getopts函数可用于解析长选项,方法是在optspec中输入破折号,后跟冒号:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#!/usr/bin/env bash
optspec=":hv-:"
while getopts"$optspec" optchar; do
    case"${optchar}" in
        -)
            case"${OPTARG}" in
                loglevel)
                    val="${!OPTIND}"; OPTIND=$(( $OPTIND + 1 ))
                    echo"Parsing option: '--${OPTARG}', value: '${val}'">&2;
                    ;;
                loglevel=*)
                    val=${OPTARG#*=}
                    opt=${OPTARG%=$val}
                    echo"Parsing option: '--${opt}', value: '${val}'">&2
                    ;;
                *)
                    if ["$OPTERR" = 1 ] && ["${optspec:0:1}" !=":" ]; then
                        echo"Unknown option --${OPTARG}">&2
                    fi
                    ;;
            esac;;
        h)
            echo"usage: $0 [-v] [--loglevel[=]<value>]">&2
            exit 2
            ;;
        v)
            echo"Parsing option: '-${optchar}'">&2
            ;;
        *)
            if ["$OPTERR" != 1 ] || ["${optspec:0:1}" =":" ]; then
                echo"Non-option argument: '-${OPTARG}'">&2
            fi
            ;;
    esac
done

复制到当前工作目录中的可执行文件name=getopts_test.sh后,可以生成如下输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$ ./getopts_test.sh
$ ./getopts_test.sh -f
Non-option argument: '-f'
$ ./getopts_test.sh -h
usage: code/getopts_test.sh [-v] [--loglevel[=]<value>]
$ ./getopts_test.sh --help
$ ./getopts_test.sh -v
Parsing option: '-v'
$ ./getopts_test.sh --very-bad
$ ./getopts_test.sh --loglevel
Parsing option: '--loglevel', value: ''
$ ./getopts_test.sh --loglevel 11
Parsing option: '--loglevel', value: '11'
$ ./getopts_test.sh --loglevel=11
Parsing option: '--loglevel', value: '11'

显然,getopts既不为长选项执行OPTERR检查,也不执行选项参数解析。上面的脚本片段显示了如何手动完成这一操作。基本原理也适用于debian almquist shell("dash")。注意特殊情况:

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

注意,正如greycat在http://mywiki.wooledge.org/bashfaq上指出的那样,这个技巧利用了shell的非标准行为,它允许将选项参数(即"-f文件名"中的文件名)连接到选项(如"-f filename")。POSIX标准指出它们之间必须有一个空格,在使用"-longoption"的情况下,它将终止选项解析,并将所有longoption转换为非选项参数。


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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
aflag=no
bflag=no
flist=""
set -- $(getopt abf:"$@")
while [ $# -gt 0 ]
do
    case"$1" in
    (-a) aflag=yes;;
    (-b) bflag=yes;;
    (-f) flist="$flist $2"; shift;;
    (--) shift; break;;
    (-*) echo"$0: error - unrecognized option $1" 1>&2; exit 1;;
    (*)  break;;
    esac
    shift
done

# Process remaining non-option arguments
...

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

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


下面是一个实际使用getopt的长选项示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
aflag=no
bflag=no
cargument=none

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

set -- $options

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


长选项可由标准getopts内置的"参数"解析为-选项的"参数"。

这是可移植的本地posix shell——不需要外部程序或bashims。

本指南将长期权作为-期权的参数,因此--alphagetopts视为-的参数,alpha--bravo=foo被视为-的参数,bravo=foo的参数。真正的论点可以通过一个简单的替代品获得:${OPTARG#*=}

在本例中,-b(及其长格式,--bravo)具有强制选项(请注意对长格式强制执行的手动重建)。长参数的非布尔选项位于等号之后,例如--bravo=foo(长选项的空格分隔符将很难实现)。

因为它使用getopts,所以该解决方案支持cmd -ac --bravo=foo -d FILE这样的用法(它将选项-a和-c组合在一起,并将长选项与标准选项交错),而这里的大多数其他答案要么很难,要么失败。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
while getopts ab:c-: arg; do
  case $arg in
    a )  ARG_A=true ;;
    b )  ARG_B="$OPTARG" ;;
    c )  ARG_C=true ;;
    - )  LONG_OPTARG="${OPTARG#*=}"
         case $OPTARG in
           alpha    )  ARG_A=true ;;
           bravo=?* )  ARG_B="$LONG_OPTARG" ;;
           bravo*   )  echo"No arg for --$OPTARG option">&2; exit 2 ;;
           charlie  )  ARG_C=true ;;
           alpha* | charlie* )
                       echo"No arg allowed for --$OPTARG option">&2; exit 2 ;;
           '' )        break ;; #"--" terminates argument processing
           * )         echo"Illegal option --$OPTARG">&2; exit 2 ;;
         esac ;;
    \? ) exit 2 ;;  # getopts already reported the illegal option
  esac
done
shift $((OPTIND-1)) # remove parsed options and args from $@ list

当参数是破折号(-时,它还有两个组件:标志名和(可选)它的参数。我用第一个等号(=号)用任何命令的标准方式来划分这些值。因此,$LONG_OPTARG只是$OPTARG的内容,没有旗名或等号。

内部case手动执行长选项,因此需要一些内务处理:

  • bravo=?--bravo=foo匹配,但不与--bravo=匹配(注:case在第一次匹配后停止)
  • bravo*如下,注意到--bravo--bravo=中缺少必要的论据。
  • alpha* | charlie*捕捉到不支持它们的选项的论据。
  • ''用于支持恰好以破折号开头的非期权。
  • *捕获所有其他长选项,并重新创建getopts为无效选项引发的错误。

你不一定需要所有这些家政用品。例如,您可能希望--bravo有一个可选的参数(由于getopts的限制,-b无法支持)。只需删除=?和相关故障案例,然后在第一次使用$ARG_B时致电${ARG_B:=$DEFAULT_ARG_B}

要接受带有空格分隔参数的长选项,您需要一个(相当安全的)eval

1
2
3
4
5
6
7
           bravo=?* )  ARG_B="$LONG_OPTARG" ;;
           bravo )     eval"ARG_B="\$$OPTIND""
                       if [ -z"$ARG_B" ]; then
                         echo"No arg for --$OPTARG option">&2; exit 2
                       fi
                       OPTIND=$((OPTIND+1)) ;;
           bravo*   )  echo"No arg for --$OPTARG option">&2; exit 2 ;;

此附加条款直接添加在=赋值版本之后,运行eval来解释当时解释选项后参数的值。bash可以使用间接扩展(EDOCX1)(42)更清晰地实现这一点,但eval需要是完全可移植的posix。$OPTIND是指下一个shell参数的数字,比如$2(意思是$1--bravo)。然后,eval将解释ARG_B="$2",因为这是附在引号中的,所以它是安全的,不会被滥用(我找不到一种方法来欺骗它做一些不适当的事情)。

保证一个非空值是非常重要的,并且需要实际检查它,因此在这种情况下,我们有一个条件并生成一个致命的错误。如果您允许它为空,则需要限制$OPTIND的递增,否则将进入一个无限循环。[ $# -gt $OPTIND ] && OPTIND=$((OPTIND+1))应该这样做。

本附加条款的最后一部分增加了$OPTIND,以正确吸收期权的论点并继续下一个期权。


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

它使添加新标志变得和向脚本中添加一行一样简单,并提供了一个自动生成的使用函数。

下面是一个使用shflag的简单Hello, world!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#!/bin/sh

# source shflags from current directory
. ./shflags

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

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

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

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

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

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

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

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


使用带有短/长选项和参数的getopts适用于所有组合,例如:

  • FoBar -F-棒
  • FoBOA-氧基-B
  • foobar-bf--巴--foobar
  • foobar-fbbashorty--bar-fb--arguments=longhorn
  • foobar-fa"text-short"-b--arguments="text longhorn"
  • 巴什福巴尔-F——巴什福罗
  • sh foobar-b--foobar-…
  • bash./foobar-f--巴

本例的一些声明

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Options=$@
Optnum=$#
sfoo='no '
sbar='no '
sfoobar='no '
sbarfoo='no '
sarguments='no '
sARG=empty
lfoo='no '
lbar='no '
lfoobar='no '
lbarfoo='no '
larguments='no '
lARG=empty

使用函数的外观

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function _usage()
{
  ###### U S A G E : Help and ERROR ######
  cat <<EOF
   foobar $Options
  $*
          Usage: foobar <[options]>
          Options:
                  -b   --bar            Set bar to yes    ($foo)
                  -f   --foo            Set foo to yes    ($bart)
                  -h   --help           Show this message
                  -A   --arguments=...  Set arguments to yes ($arguments) AND get ARGUMENT ($ARG)
                  -B   --barfoo         Set barfoo to yes ($barfoo)
                  -F   --foobar         Set foobar to yes ($foobar)
EOF

}

[ $# = 0 ] && _usage"  >>>>>>>> no options given"

带长/短标志和长参数的getops

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
while getopts ':bfh-A:BF' OPTION ; do
  case"$OPTION" in
    b  ) sbar=yes                       ;;
    f  ) sfoo=yes                       ;;
    h  ) _usage                         ;;  
    A  ) sarguments=yes;sARG="$OPTARG"  ;;
    B  ) sbarfoo=yes                    ;;
    F  ) sfoobar=yes                    ;;
    -  ) [ $OPTIND -ge 1 ] && optind=$(expr $OPTIND - 1 ) || optind=$OPTIND
         eval OPTION="\$$optind"
         OPTARG=$(echo $OPTION | cut -d'=' -f2)
         OPTION=$(echo $OPTION | cut -d'=' -f1)
         case $OPTION in
             --foo       ) lfoo=yes                       ;;
             --bar       ) lbar=yes                       ;;
             --foobar    ) lfoobar=yes                    ;;
             --barfoo    ) lbarfoo=yes                    ;;
             --help      ) _usage                         ;;
             --arguments ) larguments=yes;lARG="$OPTARG"  ;;
             * )  _usage" Long: >>>>>>>> invalid options (long)" ;;
         esac
       OPTIND=1
       shift
      ;;
    ? )  _usage"Short: >>>>>>>> invalid options (short)"  ;;
  esac
done

产量

1
2
3
4
5
6
7
##################################################################
echo"----------------------------------------------------------"
echo"RESULT short-foo      : $sfoo                                    long-foo      : $lfoo"
echo"RESULT short-bar      : $sbar                                    long-bar      : $lbar"
echo"RESULT short-foobar   : $sfoobar                                 long-foobar   : $lfoobar"
echo"RESULT short-barfoo   : $sbarfoo                                 long-barfoo   : $lbarfoo"
echo"RESULT short-arguments: $sarguments  with Argument = "$sARG"   long-arguments: $larguments and $lARG"

将上述内容组合成一个内聚的脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
#!/bin/bash
# foobar: getopts with short and long options AND arguments

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

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

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

function _usage()
{
  ###### U S A G E : Help and ERROR ######
  cat <<EOF
   foobar $Options
   $*
          Usage: foobar <[options]>
          Options:
                  -b   --bar            Set bar to yes    ($foo)
                    -f   --foo            Set foo to yes    ($bart)
                      -h   --help           Show this message
                  -A   --arguments=...  Set arguments to yes ($arguments) AND get ARGUMENT ($ARG)
                  -B   --barfoo         Set barfoo to yes ($barfoo)
                  -F   --foobar         Set foobar to yes ($foobar)
EOF

}

[ $# = 0 ] && _usage"  >>>>>>>> no options given"

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


另一种方式…

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# translate long options to short
for arg
do
    delim=""
    case"$arg" in
       --help) args="${args}-h";;
       --verbose) args="${args}-v";;
       --config) args="${args}-c";;
       # pass through anything else
       *) [["${arg:0:1}" =="-" ]] || delim="""
           args="
${args}${delim}${arg}${delim}";;
    esac
done
# reset the translated args
eval set -- $args
# now we can process with getopt
while getopts"
:hvc:" opt; do
    case $opt in
        h)  usage ;;
        v)  VERBOSE=true ;;
        c)  source $OPTARG ;;
        \?) usage ;;
        :)
        echo"
option -$OPTARG requires an argument"
        usage
        ;;
    esac
done


我是这样解决的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# A string with command options
options=$@

# An array with all the arguments
arguments=($options)

# Loop index
index=0

for argument in $options
  do
    # Incrementing index
    index=`expr $index + 1`

    # The conditions
    case $argument in
      -a) echo"key $argument value ${arguments[index]}" ;;
      -abc) echo"key $argument value ${arguments[index]}" ;;
    esac
  done

exit;

我是哑巴还是什么?getoptgetopts令人困惑。


如果您不希望getopt依赖,可以这样做:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
while test $# -gt 0
do
  case $1 in

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

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

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

  # Done with options
    *)
      break
      ;;
  esac

  # for testing purposes:
  echo"$1"

  shift
done

当然,那么你不能用长样式选项和一个破折号。如果要添加缩短的版本(例如--verbos而不是--verbos e),则需要手动添加这些版本。

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

我也把这段话放在要点中。


内置的getopts不能这样做。有一个外部getopt(1)程序可以做到这一点,但您只能从util linux包在linux上获得它。它附带了一个示例脚本getopt-parse.bash。

还有一个作为shell函数编写的getopts_long


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#!/bin/bash
while getopts"abc:d:" flag
do
  case $flag in
    a) echo"[getopts:$OPTIND]==> -$flag";;
    b) echo"[getopts:$OPTIND]==> -$flag";;
    c) echo"[getopts:$OPTIND]==> -$flag $OPTARG";;
    d) echo"[getopts:$OPTIND]==> -$flag $OPTARG";;
  esac
done

shift $((OPTIND-1))
echo"[otheropts]==> $@"

exit

.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#!/bin/bash
until [ -z"$1" ]; do
  case $1 in
   "--dlong")
      shift
      if ["${1:1:0}" !="-" ]
      then
        echo"==> dlong $1"
        shift
      fi;;
    *) echo"==> other $1"; shift;;
  esac
done
exit


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

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

我找到的教程中也有这样的说法。试试看。


发明了另一种轮子…

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

当然,这仍然是可以放到脚本中的相当大的代码块,但是它大约是著名的getopt-long shell函数的一半,并且在您只想替换现有的gnu-getopt使用的情况下可能更可取。

这是一个非常新的代码,所以YMMV(当然,请告诉我这是否因为任何原因而与POSIX不兼容——可移植性从一开始就是我的意图,但我没有一个有用的POSIX测试环境)。

代码和示例用法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
#!/bin/sh
# posix_getopt shell function
# Author: Phil S.
# Version: 1.0
# Created: 2016-07-05
# URL: http://stackoverflow.com/a/37087374/324105

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

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

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

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

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

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

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

示例用法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# Process command line options
shortopts="hvd:c::s::L:D"
longopts="help,version,directory:,client::,server::,load:,delete"
#opts=$(getopt -o"$shortopts" -l"$longopts" -n"$(basename $0)" --"$@")
opts=$(posix_getopt"$shortopts""$longopts""$@")
if [ $? -eq 0 ]; then
    #eval set -- ${opts}
    eval"set -- ${opts}"
    while [ $# -gt 0 ]; do
        case"$1" in
            ( --                ) shift; break;;
            ( -h|--help         ) help=1; shift; break;;
            ( -v|--version      ) version_help=1; shift; break;;
            ( -d|--directory    ) dir=$2; shift 2;;
            ( -c|--client       ) useclient=1; client=$2; shift 2;;
            ( -s|--server       ) startserver=1; server_name=$2; shift 2;;
            ( -L|--load         ) load=$2; shift 2;;
            ( -D|--delete       ) delete=1; shift;;
        esac
    done
else
    shorthelp=1 # getopt returned (and reported) an error.
fi

我只是偶尔写一些shell脚本,然后就不在实践中了,所以任何反馈都会受到赞赏。

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

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

将导致"loglevel"的值被视为"-toc=true"。这个罐头要避免。

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

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

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

  • 如果用户具有长/等号格式(--opt=),则后面的空格=将触发错误,因为未提供参数。
  • 如果用户有long/space参数(--opt),如果后面没有参数(命令结束)或参数以破折号开头,则此脚本将导致失败。
  • 如果您是从这个开始的,那么"-opt=value"和"-opt-value"格式之间有一个有趣的区别。使用等号,命令行参数被视为"opt=value",要处理的工作是字符串解析,在"="处分隔。相反,使用"-opt-value",参数的名称是"opt",我们面临获取命令行中提供的下一个值的挑战。这就是@arvid需要使用的地方$!optind,间接参考。我仍然不明白这一点,好吧,而且bashfaq中的评论似乎对这种风格发出了警告(http://mywiki.wooledge.org/bashfaq/006)。顺便说一句,我不认为之前海报上关于optind的重要性的评论是正确的($optind+1))。我的意思是说,我看不出省略它有什么坏处。

    在这个脚本的最新版本中,flag-v意味着详细的打印输出。

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    ./cli-5.sh  -v --loglevel=44 --toc  TRUE
    ./cli-5.sh  -v --loglevel=44 --toc=TRUE
    ./cli-5.sh --loglevel 7
    ./cli-5.sh --loglevel=8
    ./cli-5.sh -l9

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

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

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

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

    你应该考虑打开-v,因为它打印出optind和optarg的内部。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    #/usr/bin/env bash

    ## Paul Johnson
    ## 20171016
    ##

    ## Combines ideas from
    ## https://stackoverflow.com/questions/402377/using-getopts-in-bash-shell-script-to-get-long-and-short-command-line-options
    ## by @Arvid Requate, and http://mwiki.wooledge.org/BashFAQ/035

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


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

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

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


    VERBOSE=0
    loglevel=0
    toc="TRUE"

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

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

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

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



    echo"
    After Parsing values
    "

    echo"loglevel  $loglevel"
    echo"toc  $toc"


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

    我确实创建了下面的一个,我认为这是一个好的,因为它是最小的代码长期权和短期权都有效。使用这种方法,长选项还可以有多个参数。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    #!/bin/bash
    # Uses bash extensions.  Not portable as written.

    declare -A longoptspec
    longoptspec=( [loglevel]=1 ) #use associative array to declare how many arguments a long option expects, in this case we declare that loglevel expects/has one argument, long options that aren't listed i n this way will have zero arguments by default
    optspec=":h-:"
    while getopts"$optspec" opt; do
    while true; do
        case"${opt}" in
            -) #OPTARG is name-of-long-option or name-of-long-option=value
                if [["${OPTARG}" =~ .*=.* ]] #with this --key=value format only one argument is possible
                then
                    opt=${OPTARG/=*/}
                    OPTARG=${OPTARG#*=}
                    ((OPTIND--))    
                else #with this --key value1 value2 format multiple arguments are possible
                    opt="$OPTARG"
                    OPTARG=(${@:OPTIND:$((longoptspec[$opt]))})
                fi
                ((OPTIND+=longoptspec[$opt]))
                continue #now that opt/OPTARG are set we can process them as if getopts would've given us long options
                ;;
            loglevel)
              loglevel=$OPTARG
                ;;
            h|help)
                echo"usage: $0 [--loglevel[=]<value>]">&2
                exit 2
                ;;
        esac
    break; done
    done

    # End of file

    我在这方面工作了很长时间…并创建了我自己的库,您需要在主脚本中找到它。例如,请参见libopt4shell和cd2mpc。希望它有帮助!


    改进的解决方案:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    # translate long options to short
    # Note: This enable long options but disable"--?*" in $OPTARG, or disable long options after "--" in option fields.
    for ((i=1;$#;i++)) ; do
        case"$1" in
            --)
                # [ ${args[$((i-1))]} == ... ] || EndOpt=1 ;;& # DIRTY: we still can handle some execptions...
                EndOpt=1 ;;&
            --version) ((EndOpt)) && args[$i]="$1"  || args[$i]="-V";;
            # default case : short option use the first char of the long option:
            --?*) ((EndOpt)) && args[$i]="$1"  || args[$i]="-${1:2:1}";;
            # pass through anything else:
            *) args[$i]="$1" ;;
        esac
        shift
    done
    # reset the translated args
    set --"${args[@]}"

    function usage {
    echo"Usage: $0 [options] files">&2
        exit $1
    }

    # now we can process with getopt
    while getopts":hvVc:" opt; do
        case $opt in
            h)  usage ;;
            v)  VERBOSE=true ;;
            V)  echo $Version ; exit ;;
            c)  source $OPTARG ;;
            \?) echo"unrecognized option: -$opt" ; usage -1 ;;
            :)
            echo"option -$OPTARG requires an argument"
            usage -1
            ;;
        esac
    done

    shift $((OPTIND-1))
    [["$1" =="--" ]] && shift


    如果需要长的命令行选项,那么使用ksh可能更简单,只适用于getopts部分,因为这样做更容易。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    # Working Getopts Long => KSH

    #! /bin/ksh
    # Getopts Long
    USAGE="s(showconfig)"
    USAGE+="c:(createdb)"
    USAGE+="l:(createlistener)"
    USAGE+="g:(generatescripts)"
    USAGE+="r:(removedb)"
    USAGE+="x:(removelistener)"
    USAGE+="t:(createtemplate)"
    USAGE+="h(help)"

    while getopts"$USAGE" optchar ; do
        case $optchar in
        s)  echo"Displaying Configuration" ;;
            c)  echo"Creating Database $OPTARG" ;;
        l)  echo"Creating Listener LISTENER_$OPTARG" ;;
        g)  echo"Generating Scripts for Database $OPTARG" ;;
        r)  echo"Removing Database $OPTARG" ;;
        x)  echo"Removing Listener LISTENER_$OPTARG" ;;
        t)  echo"Creating Database Template" ;;
        h)  echo"Help" ;;
        esac
    done


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

    • 短杆(-h)
    • 短选项(-i"image.jpg")
    • 长鼻(帮助)
    • 等于选项(--file="filename.ext")
    • 空格选项(--file"filename.ext")
    • 固定的喷鼻(-hvm)

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    # Check if a list of params contains a specific param
    # usage: if _param_variant"h|?|help p|path f|file long-thing t|test-thing""file" ; then ...
    # the global variable $key is updated to the long notation (last entry in the pipe delineated list, if applicable)
    _param_variant() {
      for param in $1 ; do
        local variants=${param//\|/ }
        for variant in $variants ; do
          if [["$variant" ="$2" ]] ; then
            # Update the key to match the long version
            local arr=(${param//\|/ })
            let last=${#arr[@]}-1
            key="${arr[$last]}"
            return 0
          fi
        done
      done
      return 1
    }

    # Get input parameters in short or long notation, with no dependencies beyond bash
    # usage:
    #     # First, set your defaults
    #     param_help=false
    #     param_path="."
    #     param_file=false
    #     param_image=false
    #     param_image_lossy=true
    #     # Define allowed parameters
    #     allowed_params="h|?|help p|path f|file i|image image-lossy"
    #     # Get parameters from the arguments provided
    #     _get_params $*
    #
    # Parameters will be converted into safe variable names like:
    #     param_help,
    #     param_path,
    #     param_file,
    #     param_image,
    #     param_image_lossy
    #
    # Parameters without a value like"-h" or"--help" will be treated as
    # boolean, and will be set as param_help=true
    #
    # Parameters can accept values in the various typical ways:
    #     -i"path/goes/here"
    #     --image"path/goes/here"
    #     --image="path/goes/here"
    #     --image=path/goes/here
    # These would all result in effectively the same thing:
    #     param_image="path/goes/here"
    #
    # Concatinated short parameters (boolean) are also supported
    #     -vhm is the same as -v -h -m
    _get_params(){

      local param_pair
      local key
      local value
      local shift_count

      while : ; do
        # Ensure we have a valid param. Allows this to work even in -u mode.
        if [[ $# == 0 || -z $1 ]] ; then
          break
        fi

        # Split the argument if it contains"="
        param_pair=(${1//=/ })
        # Remove preceeding dashes
        key="${param_pair[0]#--}"

        # Check for concatinated boolean short parameters.
        local nodash="${key#-}"
        local breakout=false
        if [["$nodash" !="$key" && ${#nodash} -gt 1 ]]; then
          # Extrapolate multiple boolean keys in single dash notation. ie."-vmh" should translate to:"-v -m -h"
          local short_param_count=${#nodash}
          let new_arg_count=$#+$short_param_count-1
          local new_args=""
          # $str_pos is the current position in the short param string $nodash
          for (( str_pos=0; str_pos<new_arg_count; str_pos++ )); do
            # The first character becomes the current key
            if [ $str_pos -eq 0 ] ; then
              key="${nodash:$str_pos:1}"
              breakout=true
            fi
            # $arg_pos is the current position in the constructed arguments list
            let arg_pos=$str_pos+1
            if [ $arg_pos -gt $short_param_count ] ; then
              # handle other arguments
              let orignal_arg_number=$arg_pos-$short_param_count+1
              local new_arg="${!orignal_arg_number}"
            else
              # break out our one argument into new ones
              local new_arg="-${nodash:$str_pos:1}"
            fi
            new_args="$new_args "$new_arg""
          done
          # remove the preceding space and set the new arguments
          eval set --"${new_args# }"
        fi
        if ! $breakout ; then
          key="$nodash"
        fi

        # By default we expect to shift one argument at a time
        shift_count=1
        if ["${#param_pair[@]}" -gt"1" ] ; then
          # This is a param with equals notation
          value="${param_pair[1]}"
        else
          # This is either a boolean param and there is no value,
          # or the value is the next command line argument
          # Assume the value is a boolean true, unless the next argument is found to be a value.
          value=true
          if [[ $# -gt 1 && -n"$2" ]]; then
            local nodash="${2#-}"
            if ["$nodash" ="$2" ]; then
              # The next argument has NO preceding dash so it is a value
              value="$2"
              shift_count=2
            fi
          fi
        fi

        # Check that the param being passed is one of the allowed params
        if _param_variant"$allowed_params""$key" ; then
          # --key-name will now become param_key_name
          eval param_${key//-/_}="$value"
        else
          printf 'WARNING: Unknown option (ignored): %s
    '
    "$1">&2
        fi
        shift $shift_count
      done
    }

    像这样使用它:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    # Assign defaults for parameters
    param_help=false
    param_path=$(pwd)
    param_file=false
    param_image=true
    param_image_lossy=true
    param_image_lossy_quality=85

    # Define the params we will allow
    allowed_params="h|?|help p|path f|file i|image image-lossy image-lossy-quality"

    # Get the params from arguments provided
    _get_params $*

    这个公认的答案很好地指出了bash内置getopts的所有缺点。答案的结尾是:

    So while it is possible to write more code to work around the lack of support for long options, this is a lot more work and partially defeats the purpose of using a getopt parser to simplify your code.

    尽管原则上我同意这一说法,但我认为我们在各种脚本中实现此功能的次数足以证明我们在创建一个"标准化的、经过良好测试的解决方案时付出了一些努力。

    因此,我已经"升级"了在getopts中构建的bash,在纯bash中实现getopts_long,没有外部依赖性。该功能的使用与内置的getopts完全兼容。

    通过在脚本中包含getopts_long(位于github上,网址为https://github.com/umkadk/getopts-long),原始问题的答案可以实现为:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    source"${PATH_TO}/getopts_long.bash"

    while getopts_long ':c: copyfile:' OPTKEY; do
        case ${OPTKEY} in
            'c'|'copyfile')
                echo 'file supplied -- ${OPTARG}'
                ;;
            '?')
                echo"INVALID OPTION -- ${OPTARG}">&2
                exit 1
                ;;
            ':')
                echo"MISSING ARGUMENT for option -- ${OPTARG}">&2
                exit 1
                ;;
            *)
                echo"Misconfigured OPTSPEC or uncaught option -- ${OPTKEY}">&2
                exit 1
                ;;
        esac
    done

    shift $(( OPTIND - 1 ))
    [["${1}" =="--" ]] && shift

    我还没有足够的代表来评论或投票他的解决方案,但SME的回答对我来说非常有效。我遇到的唯一问题是,这些论点最后都用单引号括起来了(所以我把它们去掉了)。

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    #!/bin/bash

    # getopt example
    # from: https://stackoverflow.com/questions/402377/using-getopts-in-bash-shell-script-to-get-long-and-short-command-line-options
    HELP_TEXT=\
    "   USAGE:

        Accepts - and -- flags, can specify options that require a value, and can be in any order. A double-hyphen (--) will stop processing options.



        Accepts the following forms:



        getopt-example.sh -a -b -c value-for-c some-arg

        getopt-example.sh -c value-for-c -a -b some-arg

        getopt-example.sh -abc some-arg

        getopt-example.sh --along --blong --clong value-for-c -a -b -c some-arg

        getopt-example.sh some-arg --clong value-for-c

        getopt-example.sh
    "


    aflag=false
    bflag=false
    cargument=""

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

    set -- $options

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

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

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

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

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

    done

    如果简单地说,这就是您想要调用脚本的方式

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

    然后,您可以使用getopt和--longoptions的帮助,按照这个最简单的方法来实现它。

    试试这个,希望这个有用

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    # Read command line options
    ARGUMENT_LIST=(
       "input1"
       "input2"
       "input3"
    )



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


    echo $opts

    eval set --$opts

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


    如果所有长选项都具有唯一性和匹配性,则第一个字符作为短选项,例如

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

    是一样的

    1
    ./slamm -c 23 -p test -q

    您可以在getopts重写$args之前使用它:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    # change long options to short options

    for arg; do
        [["${arg:0:1}" =="-" ]] && delim="" || delim="""
        if ["
    ${arg:0:2}" =="--" ];
           then args="
    ${args} -${arg:2:1}"
           else args="
    ${args} ${delim}${arg}${delim}"
        fi
    done

    # reset the incoming args
    eval set -- $args

    # proceed as usual
    while getopts"
    :b:la:h" OPTION; do
        .....

    感谢Mtvee的启发;-)


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

    我发现它很容易使用,下面是一个例子:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    ArgParser::addArg"[h]elp"    false   "This list"
    ArgParser::addArg"[q]uiet"   false   "Supress output"
    ArgParser::addArg"[s]leep"   1       "Seconds to sleep"
    ArgParser::addArg"v"         1       "Verbose mode"

    ArgParser::parse"$@"

    ArgParser::isset help && ArgParser::showArgs

    ArgParser::isset"quiet" \
       && echo"Quiet!" \
       || echo"Noisy!"

    local __sleep
    ArgParser::tryAndGetArg sleep into __sleep \
       && echo"Sleep for $__sleep seconds" \
       || echo"No value passed for sleep"

    # This way is often more convienient, but is a little slower
    echo"Sleep set to: $( ArgParser::getArg sleep )"

    所需的bash比它可能要长一些,但我想避免依赖bash 4的关联数组。您也可以直接从http://nt4.com/bash/argparser.inc.sh下载此文件。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    197
    198
    199
    200
    201
    202
    203
    204
    205
    206
    207
    208
    209
    210
    211
    212
    213
    214
    215
    216
    217
    218
    219
    220
    221
    222
    223
    224
    225
    226
    227
    228
    229
    230
    231
    232
    233
    234
    235
    236
    237
    238
    239
    240
    241
    242
    243
    244
    245
    246
    247
    248
    249
    250
    251
    252
    253
    254
    255
    256
    257
    258
    259
    260
    261
    262
    263
    264
    265
    266
    267
    268
    269
    270
    271
    272
    273
    274
    275
    #!/usr/bin/env bash

    # Updates to this script may be found at
    # http://nt4.com/bash/argparser.inc.sh

    # Example of runtime usage:
    # mnc.sh --nc -q Caprica.S0*mkv *.avi *.mp3 --more-options here --host centos8.host.com

    # Example of use in script (see bottom)
    # Just include this file in yours, or use
    # source argparser.inc.sh

    unset EXPLODED
    declare -a EXPLODED
    function explode
    {
        local c=$#
        (( c < 2 )) &&
        {
            echo function"$0" is missing parameters
            return 1
        }

        local delimiter="$1"
        local string="$2"
        local limit=${3-99}

        local tmp_delim=$'\x07'
        local delin=${string//$delimiter/$tmp_delim}
        local oldifs="$IFS"

        IFS="$tmp_delim"
        EXPLODED=($delin)
        IFS="$oldifs"
    }


    # See: http://fvue.nl/wiki/Bash:_Passing_variables_by_reference
    # Usage: local"$1" && upvar $1"value(s)"
    upvar() {
        if unset -v"$1"; then           # Unset & validate varname
            if (( $# == 2 )); then
                eval $1="\$2"          # Return single value
            else
                eval $1=\("\${@:2}"\)  # Return array
            fi
        fi
    }

    function decho
    {
        :
    }

    function ArgParser::check
    {
        __args=${#__argparser__arglist[@]}
        for (( i=0; i<__args; i++ ))
        do
            matched=0
            explode"|""${__argparser__arglist[$i]}"
            if ["${#1}" -eq 1 ]
            then
                if ["${1}" =="${EXPLODED[0]}" ]
                then
                    decho"Matched $1 with ${EXPLODED[0]}"
                    matched=1

                    break
                fi
            else
                if ["${1}" =="${EXPLODED[1]}" ]
                then
                    decho"Matched $1 with ${EXPLODED[1]}"
                    matched=1

                    break
                fi
            fi
        done
        (( matched == 0 )) && return 2
        # decho"Key $key has default argument of ${EXPLODED[3]}"
        if ["${EXPLODED[3]}" =="false" ]
        then
            return 0
        else
            return 1
        fi
    }

    function ArgParser::set
    {
        key=$3
        value="${1:-true}"
        declare -g __argpassed__$key="$value"
    }

    function ArgParser::parse
    {

        unset __argparser__argv
        __argparser__argv=()
        # echo parsing:"$@"

        while [ -n"$1" ]
        do
            # echo"Processing $1"
            if ["${1:0:2}" == '--' ]
            then
                key=${1:2}
                value=$2
            elif ["${1:0:1}" == '-' ]
            then
                key=${1:1}               # Strip off leading -
                value=$2
            else
                decho"Not argument or option: '$1'">& 2
                __argparser__argv+=("$1" )
                shift
                continue
            fi
            # parameter=${tmp%%=*}     # Extract name.
            # value=${tmp##*=}         # Extract value.
            decho"Key: '$key', value: '$value'"
            # eval $parameter=$value
            ArgParser::check $key
            el=$?
            # echo"Check returned $el for $key"
            [ $el -eq  2 ] && decho"No match for option '$1'">&2 # && __argparser__argv+=("$1" )
            [ $el -eq  0 ] && decho"Matched option '${EXPLODED[2]}' with no arguments"        >&2 && ArgParser::set true"${EXPLODED[@]}"
            [ $el -eq  1 ] && decho"Matched option '${EXPLODED[2]}' with an argument of '$2'"   >&2 && ArgParser::set"$2""${EXPLODED[@]}" && shift
            shift
        done
    }

    function ArgParser::isset
    {
        declare -p"__argpassed__$1"> /dev/null 2>&1 && return 0
        return 1
    }

    function ArgParser::getArg
    {
        # This one would be a bit silly, since we can only return non-integer arguments ineffeciently
        varname="__argpassed__$1"
        echo"${!varname}"
    }

    ##
    # usage: tryAndGetArg  into <varname>
    # returns: 0 on success, 1 on failure
    function ArgParser::tryAndGetArg
    {
        local __varname="__argpassed__$1"
        local __value="${!__varname}"
        test -z"$__value" && return 1
        local"$3" && upvar $3"$__value"
        return 0
    }

    function ArgParser::__construct
    {
        unset __argparser__arglist
        # declare -a __argparser__arglist
    }

    ##
    # @brief add command line argument
    # @param 1 short and/or long, eg: [s]hort
    # @param 2 default value
    # @param 3 description
    ##
    function ArgParser::addArg
    {
        # check for short arg within long arg
        if [["$1" =~ \[(.)\] ]]
        then
            short=${BASH_REMATCH[1]}
            long=${1/\[$short\]/$short}
        else
            long=$1
        fi
        if ["${#long}" -eq 1 ]
        then
            short=$long
            long=''
        fi
        decho short:"$short"
        decho long:"$long"
        __argparser__arglist+=("$short|$long|$1|$2|$3")
    }

    ##
    # @brief show available command line arguments
    ##
    function ArgParser::showArgs
    {
        # declare -p | grep argparser
        printf"Usage: %s [OPTION...]

    "
    "$( basename"${BASH_SOURCE[0]}" )"
        printf"Defaults for the options are specified in brackets.

    "
    ;

        __args=${#__argparser__arglist[@]}
        for (( i=0; i<__args; i++ ))
        do
            local shortname=
            local fullname=
            local default=
            local description=
            local comma=

            explode"|""${__argparser__arglist[$i]}"

            shortname="${EXPLODED[0]:+-${EXPLODED[0]}}" # String Substitution Guide:
            fullname="${EXPLODED[1]:+--${EXPLODED[1]}}" # http://tldp.org/LDP/abs/html/parameter-substitution.html
            test -n"$shortname" \
                && test -n"$fullname" \
                && comma=","

            default="${EXPLODED[3]}"
            case $default in
                false )
                    default=
                    ;;
               "" )
                    default=
                    ;;
                * )
                    default="[$default]"
            esac

            description="${EXPLODED[4]}"

            printf"  %2s%1s %-19s %s %s
    "
    "$shortname""$comma""$fullname""$description""$default"
        done
    }

    function ArgParser::test
    {
        # Arguments with a default of 'false' do not take paramaters (note: default
        # values are not applied in this release)

        ArgParser::addArg"[h]elp"      false      "This list"
        ArgParser::addArg"[q]uiet" false      "Supress output"
        ArgParser::addArg"[s]leep" 1          "Seconds to sleep"
        ArgParser::addArg"v"           1          "Verbose mode"

        ArgParser::parse"$@"

        ArgParser::isset help && ArgParser::showArgs

        ArgParser::isset"quiet" \
            && echo"Quiet!" \
            || echo"Noisy!"

        local __sleep
        ArgParser::tryAndGetArg sleep into __sleep \
            && echo"Sleep for $__sleep seconds" \
            || echo"No value passed for sleep"

        # This way is often more convienient, but is a little slower
        echo"Sleep set to: $( ArgParser::getArg sleep )"

        echo"Remaining command line: ${__argparser__argv[@]}"

    }

    if ["$( basename"$0" )" =="argparser.inc.sh" ]
    then
        ArgParser::test"$@"
    fi

    EasyOptions处理短选项和长选项:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    ## Options:
    ##   --verbose, -v   Verbose mode
    ##   --logfile=NAME  Log filename

    source easyoptions || exit

    if test -n"${verbose}"; then
        echo"log file: ${logfile}"
        echo"arguments: ${arguments[@]}"
    fi

    builtin getopts只分析短选项(ksh93除外),但您仍然可以添加几行脚本,使getopts处理长选项。

    以下是http://www.uxora.com/unix/shell-script/22-handle-long-options-with-getopts中的一部分代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
      #== set short options ==#
    SCRIPT_OPTS=':fbF:B:-:h'
      #== set long options associated with short one ==#
    typeset -A ARRAY_OPTS
    ARRAY_OPTS=(
        [foo]=f
        [bar]=b
        [foobar]=F
        [barfoo]=B
        [help]=h
        [man]=h
    )

      #== parse options ==#
    while getopts ${SCRIPT_OPTS} OPTION ; do
        #== translate long options to short ==#
        if [["x$OPTION" =="x-" ]]; then
            LONG_OPTION=$OPTARG
            LONG_OPTARG=$(echo $LONG_OPTION | grep"=" | cut -d'=' -f2)
            LONG_OPTIND=-1
            [["x$LONG_OPTARG" ="x" ]] && LONG_OPTIND=$OPTIND || LONG_OPTION=$(echo $OPTARG | cut -d'=' -f1)
            [[ $LONG_OPTIND -ne -1 ]] && eval LONG_OPTARG="\$$LONG_OPTIND"
            OPTION=${ARRAY_OPTS[$LONG_OPTION]}
            [["x$OPTION" ="x" ]] &&  OPTION="?" OPTARG="-$LONG_OPTION"

            if [[ $( echo"${SCRIPT_OPTS}" | grep -c"${OPTION}:" ) -eq 1 ]]; then
                if [["x${LONG_OPTARG}" ="x" ]] || [["${LONG_OPTARG}" = -* ]]; then
                    OPTION=":" OPTARG="-$LONG_OPTION"
                else
                    OPTARG="$LONG_OPTARG";
                    if [[ $LONG_OPTIND -ne -1 ]]; then
                        [[ $OPTIND -le $Optnum ]] && OPTIND=$(( $OPTIND+1 ))
                        shift $OPTIND
                        OPTIND=1
                    fi
                fi
            fi
        fi

        #== options follow by another option instead of argument ==#
        if [["x${OPTION}" !="x:" ]] && [["x${OPTION}" !="x?" ]] && [["${OPTARG}" = -* ]]; then
            OPTARG="$OPTION" OPTION=":"
        fi

        #== manage options ==#
        case"$OPTION" in
            f  ) foo=1 bar=0                    ;;
            b  ) foo=0 bar=1                    ;;
            B  ) barfoo=${OPTARG}               ;;
            F  ) foobar=1 && foobar_name=${OPTARG} ;;
            h ) usagefull && exit 0 ;;
            : ) echo"${SCRIPT_NAME}: -$OPTARG: option requires an argument">&2 && usage >&2 && exit 99 ;;
            ? ) echo"${SCRIPT_NAME}: -$OPTARG: unknown option">&2 && usage >&2 && exit 99 ;;
        esac
    done
    shift $((${OPTIND} - 1))

    这是一个测试:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    # Short options test
    $ ./foobar_any_getopts.sh -bF"Hello world" -B 6 file1 file2
    foo=0 bar=1
    barfoo=6
    foobar=1 foobar_name=Hello world
    files=file1 file2

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

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


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


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

    用途:

    1
    2
    3
    4
    5
    6
    7
    8
    $ ./test-args.sh --a1 a1 --a2"a 2" --a3 --a4= --a5=a5 --a6="a 6"
    a1 ="a1"
    a2 ="a 2"
    a3 ="TRUE"
    a4 =""
    a5 ="a5"
    a6 ="a 6"
    a7 =""

    脚本:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    #!/bin/bash

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

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

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

        exit 0
    }


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


    function getNamedArg() {
        ARG_NAME=$1

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

    }


    main"$@"

    getopts"could be used"用于解析长选项,只要您不希望它们有参数…

    以下是如何:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    $ cat > longopt
    while getopts 'e:-:' OPT; do
      case $OPT in
        e) echo echo: $OPTARG;;
        -) #long option
           case $OPTARG in
             long-option) echo long option;;
             *) echo long option: $OPTARG;;
           esac;;
      esac
    done

    $ bash longopt -e asd --long-option --long1 --long2 -e test
    echo: asd
    long option
    long option: long1
    long option: long2
    echo: test

    如果尝试使用optind获取long选项的参数,getopts会将其视为第一个非可选位置参数,并停止分析任何其他参数。在这种情况下,您最好用一个简单的case语句手动处理它。

    这将"始终"起作用:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    $ cat >longopt2
    while (($#)); do
        OPT=$1
        shift
        case $OPT in
            --*) case ${OPT:2} in
                long1) echo long1 option;;
                complex) echo comples with argument $1; shift;;
            esac;;

            -*) case ${OPT:1} in
                a) echo short option a;;
                b) echo short option b with parameter $1; shift;;
            esac;;
        esac
    done


    $ bash longopt2 --complex abc -a --long -b test
    comples with argument abc
    short option a
    short option b with parameter test

    尽管不像getopts那么灵活,但是您必须自己在case实例中检查代码…

    但这是一种选择。


    嗯。

    对纯bash选项不太满意。为什么不使用Perl来得到你想要的呢?直接分析$*数组,并自动命名选项。

    简单帮助程序脚本:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    #!/usr/bin/perl
    use Getopt::Long;

    my $optstring = shift;

    my @opts = split(m#,#, $optstring);

    my %opt;
    GetOptions(\%opt, @opts);

    print"set --" . join(' ', map("'$_'", @ARGV)) .";";
    my $xx;

    my $key;
    foreach $key (keys(%opt))
    {
        print"export $key='$opt{$key}';";
    }

    然后,您可以在脚本中使用一行程序,例如:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    #!/bin/bash

    eval `getopts.pl reuse:s,long_opt:s,hello $*`;

    echo"HELLO: $hello"
    echo"LONG_OPT: $long_opt"
    echo"REUSE: $reuse"

    echo $*

    /tmp/script.sh hello——重用我——long——选择你想要的任何东西——除了空格——hello 1 2 3

    你好:1long-opt:你想要的任何东西,除了空格再利用:我

    1 2 2

    这里唯一要注意的是空间不起作用。但它避免了bash相当复杂的循环语法,可以使用长参数,自动将它们命名为变量,并自动调整$*的大小,因此99%的时间都可以使用。