我希望有长期和短形式的命令行的选择援引使用我的外壳脚本。

我知道, getopts 可以使用,但是,像在Perl,我不能做同样的外壳。

任何想法,如何可以做到这一点,因此,我可以利用的选择,如:

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

在上述两个命令的意思是相同的事情我的外壳,但使用 getopts, 我没有能够实现这些?

有帮助吗?

解决方案

有三个实现,可以考虑:

  • Bash builtin getopts.这并不支持选项名称的双划的前缀。它只支持单字的选项。

  • BSD UNIX执行情况的独立 getopt 命令(这是什么mac os使用的).这并不支持长期的选择。

  • GNU执行情况的独立 getopt.GNU getopt(3) (使用由的命令行 getopt(1) 在Linux)支持分析长期选择。


其他一些答复显示出解决方案使用bash builtin getopts 模仿长的选择。这种解决办法实际上使一个很短的选项他的角色是"-".所以你能"--"作为标志。然后什么以下变得OPTARG,你的测试的OPTARG有一套 case.

这是聪明的,但它与需要注意的事项:

  • getopts 不能强制执行选择的规范。它不可能返回的错误,如果用户提供一个无效的选择。你必须要做你自己的错误检查你分析OPTARG.
  • OPTARG是用于长时间的选择的名称,其中复杂的使用情况时,你的长项自身具有的一个参数。你最终有代码自己作为一个附加的情况。

因此,尽管它可能写入更多的代码的工作缺乏支持长期选择,这是一个更多的工作和部分失败的目的使用getopt分析程序的简化。

其他提示

getoptgetopts 是不同的野兽,人们似乎有一点误解为什么他们这样做。 getopts 是一个内置命令 bash 要处理的命令行的选择在一个循环,并分配每个找到的选择和价值转到内在的变量,这样你就可以进一步处理它们。 getopt, 但是,外部应用程序,并且它 实际上并不处理你的选择你 这种方式,例如bash getopts, ,Perl Getopt 模块或蟒蛇 optparse/argparse 模块做。所有 getopt 不会是规范化的选项是通过在即将它们转化为一个更标准的形式,使它更容易为一个脚本来处理它们。例如,应用程序的 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-m4096 风格,混合选择和非选择的任何顺序,等等。 getopt 还输出错误的信息,如果无法识别或模棱两可的选择是找到。

注意到:实际上有两个 完全不同 版本的 getopt, 的、基本的 getopt 和GNU getopt, 与不同的特点和不同的电话公约。2 基本的 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 是在你的外壳,今后的道路的 /usr/bin.FreeBSD上,安装 misc/getopt.

一个快速指南,以修改的例代码你自己的程序:第几条线,所有是"样板文件",应该保持相同,除了线电话 getopt.你应该改变程序的名称之后 -n, 指定的短选项之后 -o, 和长选项之后 --long.把一个结肠后的选项,采取一个数值。

最后,如果你看到码刚刚 set 而不是的 eval set, 它是书面的BSD getopt.你应该改变它的使用 eval set 风格,其工作的现有的两个版本 getopt, 同时纯 set 不工作的权利与GNU getopt.

1实际上, getoptsksh93 支持长名称的选择,但这个壳是不是用作通常作为 bash.在 zsh, 使用 zparseopts 要获得这种功能。

2从技术上讲,"GNU getopt"一词不当;这个版本其实是写为Linux而不是GNU项目。然而,它遵循所有GNU公约,并将术语"GNU getopt"通常使用(例如在FreeBSD).

Bash builtin 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: 
$ ./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'
[-v] [--loglevel[=]<value>]" >&2 exit 2 ;; v) echo "Parsing option: '-${optchar}'" >&2 ;; *) if [ "$OPTERR" != 1 ] || [ "${optspec:0:1}" = ":" ]; then echo "Non-option argument: '-${OPTARG}'" >&2 fi ;; esac done

当前工作目录中复制到可执行文件名= getopts_test.sh 之后,可以产生类似

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

显然,getopts既不执行 OPTERR 检查,也不对long选项执行option-argument解析。上面的脚本片段显示了如何手动完成此操作。基本原理也适用于Debian Almquist shell(“dash”)。请注意特殊情况:

<*>

请注意,来自 http://mywiki.wooledge.org/BashFAQ 的GreyCat指出out,这个技巧利用了shell的非标准行为,它允许将option-argument(即“-f filename”中的文件名)连接到该选项(如“-ffilename”中所示)。 POSIX 标准说它们之间必须有一个空格, “ - longoption”的情况将终止选项解析并将所有longoptions转换为非选项参数。

内置的 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 "<*>: error - unrecognized option $1" 1>&2; exit 1;;
    (*)  break;;
    esac
    shift
done

# Process remaining non-option arguments
...

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

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

这是一个实际使用带有长选项的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 "<*>: error - unrecognized option $1" 1>&2; exit 1;;
    (*) break;;
    esac
    shift
done

长期的选择可以加以分析的标准 getopts 内作为"参数"的 - "选项"

这是便携式和地POSIX壳没有外程序或bashisms是必要的。

这个指南,实现了长期的选择作为参数 - 选项,所以 --alphagetopts 作为 - 有的论点 alpha--bravo=foo 被认为 - 有的论点 bravo=foo.真正的论点可以是收获一个简单的替换: ${OPTARG#*=}.

在这个例子中, -b (及其长形式, --bravo)具有强制性的选项(说明手册的重建执行,为长形式)。非布尔选项,只要参数后来均等的迹象,例如 --bravo=foo (空间分隔长的选择,将很难实现).

因为这个用途的 getopts, 这解决方案支持使用象 cmd -ac --bravo=foo -d FILE (已经合并的选择 -ac 和交织长的选择标准的选择)虽然大多数其他的答案在这里斗争,或无法做到这一点。

while getopts ab:c-: arg; do
  case $arg in
    a )  ARG_A=true ;;
    b )  ARG_B="$OPTARG" ;;
    c )  ARG_C=true ;;
    - )  LONG_OPTARG="${OPTARG#*=}"
         case $OPTARG in
           alpha    )  ARG_A=true ;;
           bravo=?* )  ARG_B="$LONG_OPTARG" ;;
           bravo*   )  echo "No arg for --$OPTARG option" >&2; exit 2 ;;
           charlie  )  ARG_C=true ;;
           alpha* | charlie* )
                       echo "No arg allowed for --$OPTARG option" >&2; exit 2 ;;
           '' )        break ;; # "--" terminates argument processing
           * )         echo "Illegal option --$OPTARG" >&2; exit 2 ;;
         esac ;;
    \? ) exit 2 ;;  # getopts already reported the illegal option
  esac
done
shift $((OPTIND-1)) # remove parsed options and args from $@ list

当这个参数是一个破折号(-),它有两个组成部分:该标志的名字(可选)其参数。我界定这些标准的方式的任何命令,第一个等号(=). $LONG_OPTARG 因此,仅仅是内容的 $OPTARG 没有标志的姓名或等号。

case 实现长期的选择手动,所以它需要某些客房服务:

  • bravo=? 比赛 --bravo=foo 但不是 --bravo= (注: case 停止之后的第一场比赛)
  • bravo* 如下所示,注意到缺少必需的参数 --bravo--bravo=
  • alpha* | charlie* 抓住给出的参数的选择不支持他们
  • '' 是本,以支持非选择这发生在启动破折号
  • * 抓住所有其他的长期选择,并重新创建的错误引发的第二个为无效的选择

你不一定需要所有这些客房服务的项目。例如,也许你想要 --bravo 有一个 可选的 参数(其中 -b 不能支持由于限制 getopts).仅仅删除 =? 和相关的故障的情况下然后叫 ${ARG_B:=$DEFAULT_ARG_B} 你第一次使用 $ARG_B.


接受长期选择与空间分隔的论点,你需要一个(安全体面) eval:

           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可以这样做更清洁的使用 间接膨胀, ARG_B="${!OPTIND}" 和aix系统管理的各个方面和扩展可以做到这一点的 参数的膨胀 P 标志, ARGB="${(P)OPTIND}", 但 eval 需要充分的便携式POSIX. $OPTIND 是一个数字指的下一个壳的论据,说 $2 (含义 $1--bravo).的 eval 然后会解释 ARG_B="$2" 因为这是封闭在报价,它是安全的不受虐待(我不能找到一种方法来招它做一些不合适的).

保证非空值是重要的,需要实际检查,因此,我们有一个条件并产生一个致命的错误在这种情况。如果你允许它是空的,你会需要conditionalize递增 $OPTIND 或其他碰到一个无限的循环。 [ $# -gt $OPTIND ] && OPTIND=$((OPTIND+1)) 应该做的。

最后一部分,此外条款的增量 $OPTIND 要适当地吸收选择的参数和移动到下一个选项。

查看 shFlags ,这是一个便携式shell库(含义) :Linux,Solaris等上的sh,bash,dash,ksh,zsh。)。

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

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

#!/bin/sh

# source shflags from current directory
. ./shflags

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

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

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

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

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

对于其他人,您必须使用短选项:

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

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

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


适用于所有组合中,电子.g:

  • foobar-f--吧
  • foobar--foo-b
  • foobar息--条--foobar
  • foobar-fbFBAshorty--吧-FB--参数=豪恩
  • foobar-fA"文矮子"-B--参数="text长角"
  • bash foobar-F--barfoo
  • sh foobar-B--foobar-...
  • bash./foobar-F--吧

一些声明对这个例子

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

如何使用功能会看

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

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

getops 与长期/短标志以及长期参数

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

输出

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

结合上述成一个有凝聚力的脚本

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

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

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

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

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

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

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

另一种方式......

# 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

我有点解决这个问题:

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

我是愚蠢还是什么? getopt getopts 令人困惑。

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

我还将此代码段放在要点中。

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

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

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

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

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

或者我发现的教程已经说过了。试试吧,看看。

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

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

当然,这仍然是一大块代码可以放入脚本中,但它大约是众所周知的getopt_long shell函数的一半,并且在你只想替换现有的GNU getopt用法的情况下可能更好。

这是一个非常新的代码,所以YMMV(当然,请告诉我,如果由于任何原因它实际上不是POSIX兼容的 - 从一开始就是可移植性,但我没有一个有用的POSIX测试环境)。

代码和示例用法如下:

#!/bin/sh
# posix_getopt shell function
# Author: Phil S.
# Version: 1.0
# Created: 2016-07-05
# URL: http://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 <*>)" -- "$@")
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提出的策略,我们注意到一些用户错误。忘记包含值的用户将意外地将下一个选项的名称视为值:

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

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

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

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

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

  1. 如果用户具有长/等号格式(--opt =),则=之后的空格会触发错误,因为未提供参数。
  2. 如果用户有长/空参数(--opt),如果后面没有参数(命令结束)或参数以破折号开头,则此脚本会导致失败)
  3. 如果你开始这个,“ - 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: <*> [-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: <*> [-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/ComplexOptionParsing

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

#!/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: <*> [--loglevel[=]<value>]" >&2
            exit 2
            ;;
    esac
break; done
done

# End of file

我已经在这个主题上工作了很长时间......并且创建了我自己的库,你需要在你的主脚本中找到它。 请参见 libopt4shell cd2mpc 的例子。 希望它有所帮助!

改进的解决方案:

# 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: <*> [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部分,如果需要长命令行选项,因为它可以更容易在那里完成。

# 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

我还没有足够的代表来评论或投票他的解决方案,但 sme的回答非常有效我。我遇到的唯一问题是参数最终包含在单引号中(所以我将它们剥离出来)。

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

#!/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 "<*>: 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),并且我需要它的工作,甚至对老年庆典的版本。这个处理各种类型的参数:

  • 短弹球(h)
  • 短期权(-我"image.jpg")
  • 只要弹球(--帮助)
  • 平等的选择(文件--="文件。ext")
  • 空间选择(文件--"的文件名。ext")
  • concatinated弹球(-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 $*

接受的答案非常好地指出了bash内置 getopts 的所有缺点。答案结束于:

  

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

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

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

包含 getopts_long (在 https:// github上)托管在GitHub上。 com / UmkaDK / getopts_long )在脚本中,原始问题的答案可以简单地实现:

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

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

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

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 

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

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

#!/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 "<*>" is missing parameters 
        return 1
    }

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

    local tmp_delim=

所需的BASH比它可能长一点,但我想避免依赖BASH 4的关联数组。您也可以直接从 http://nt4.com/bash/argparser.inc.sh <下载此内容。 / A>

<*>_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 <下载此内容。 / A>

<*>\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 "

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

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

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 

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

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

<*>

所需的BASH比它可能长一点,但我想避免依赖BASH 4的关联数组。您也可以直接从 http://nt4.com/bash/argparser.inc.sh <下载此内容。 / A>

<*>_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 <下载此内容。 / A>

<*>_value" && return 1 local "$3" && upvar $3 "

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

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

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 

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

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

<*>

所需的BASH比它可能长一点,但我想避免依赖BASH 4的关联数组。您也可以直接从 http://nt4.com/bash/argparser.inc.sh <下载此内容。 / A>

<*>_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 <下载此内容。 / A>

<*>_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

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

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

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 

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

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

<*>

所需的BASH比它可能长一点,但我想避免依赖BASH 4的关联数组。您也可以直接从 http://nt4.com/bash/argparser.inc.sh <下载此内容。 / A>

<*>_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 <下载此内容。 / A>

<*>_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 "<*>" )" == "argparser.inc.sh" ] then ArgParser::test "$@" fi

所需的BASH比它可能长一点,但我想避免依赖BASH 4的关联数组。您也可以直接从 http://nt4.com/bash/argparser.inc.sh <下载此内容。 / A>

<*>_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 <下载此内容。 / A>

<*>

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

./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的灵感; - )

内置 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 /壳脚本/ 20-getopts的与 - 人页和 - 长选项

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

EasyOptions 处理短期和长期期权:

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

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

如果只是这样你想要调用脚本

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 "<*>")" \
    --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
许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top