说,我有一个脚本与此行一起调用:
./myscript -vfd ./foo/bar/someFile -o /fizz/someOtherFile

或这个脚本:
./myscript -v -f -d -o /fizz/someOtherFile ./foo/bar/someFile 

解析此脚本的方式是什么,在每种情况下(或某些情况下)两者的组合)$v$f$d都将设置为true,并且$outFile将等于/fizz/someOtherFile

评论

对于zsh用户,有一个很棒的内置程序zparseopts可以执行:zparseopts -D -E -M-d = debug -debug = d并且$ debug数组中的-d和--debug都具有echo $ + debug [1 ]将返回0或1(如果使用其中之一)。参考:zsh.org/mla/users/2011/msg00350.html

很好的教程:linuxcommand.org/lc3_wss0120.php。我特别喜欢“命令行选项”示例。

我创建了一个脚本为您执行此操作,该脚本称为-github.com/unfor19/bargs

另请参见给bash脚本提供接受标志的选项,例如命令?一个精心设计的临时多头和空头选项解析器。它不会尝试处理短选项附带的选项参数,也不会尝试使用=将选项名称与选项值分开的长选项(在两种情况下,仅假设选项值位于下一个参数中)。它还不能处理短期权集群-问题不需要它。

#1 楼

方法#1:使用不带getopt [s]的bash

传递键值对参数的两种常见方法是:

Bash分隔空格(例如--option argument)(没有getopt [s])

用法demo-space-separated.sh -e conf -s /etc -l /usr/lib /etc/hosts



 cat >/tmp/demo-space-separated.sh <<'EOF'
#!/bin/bash

POSITIONAL=()
while [[ $# -gt 0 ]]
do
key=""

case $key in
    -e|--extension)
    EXTENSION=""
    shift # past argument
    shift # past value
    ;;
    -s|--searchpath)
    SEARCHPATH=""
    shift # past argument
    shift # past value
    ;;
    -l|--lib)
    LIBPATH=""
    shift # past argument
    shift # past value
    ;;
    --default)
    DEFAULT=YES
    shift # past argument
    ;;
    *)    # unknown option
    POSITIONAL+=("") # save it in an array for later
    shift # past argument
    ;;
esac
done
set -- "${POSITIONAL[@]}" # restore positional parameters

echo "FILE EXTENSION  = ${EXTENSION}"
echo "SEARCH PATH     = ${SEARCHPATH}"
echo "LIBRARY PATH    = ${LIBPATH}"
echo "DEFAULT         = ${DEFAULT}"
echo "Number files in SEARCH PATH with EXTENSION:" $(ls -1 "${SEARCHPATH}"/*."${EXTENSION}" | wc -l)
if [[ -n  ]]; then
    echo "Last line of file specified as non-opt/last argument:"
    tail -1 ""
fi
EOF

chmod +x /tmp/demo-space-separated.sh

/tmp/demo-space-separated.sh -e conf -s /etc -l /usr/lib /etc/hosts
 


复制粘贴以上块的输出:

 FILE EXTENSION  = conf
SEARCH PATH     = /etc
LIBRARY PATH    = /usr/lib
DEFAULT         =
Number files in SEARCH PATH with EXTENSION: 14
Last line of file specified as non-opt/last argument:
#93.184.216.34    example.com
 


Bash等号分隔(例如,--option=argument)(不带getopt [s])

用法demo-equals-separated.sh -e=conf -s=/etc -l=/usr/lib /etc/hosts

 cat >/tmp/demo-equals-separated.sh <<'EOF'
#!/bin/bash

for i in "$@"
do
case $i in
    -e=*|--extension=*)
    EXTENSION="${i#*=}"
    shift # past argument=value
    ;;
    -s=*|--searchpath=*)
    SEARCHPATH="${i#*=}"
    shift # past argument=value
    ;;
    -l=*|--lib=*)
    LIBPATH="${i#*=}"
    shift # past argument=value
    ;;
    --default)
    DEFAULT=YES
    shift # past argument with no value
    ;;
    *)
          # unknown option
    ;;
esac
done
echo "FILE EXTENSION  = ${EXTENSION}"
echo "SEARCH PATH     = ${SEARCHPATH}"
echo "LIBRARY PATH    = ${LIBPATH}"
echo "DEFAULT         = ${DEFAULT}"
echo "Number files in SEARCH PATH with EXTENSION:" $(ls -1 "${SEARCHPATH}"/*."${EXTENSION}" | wc -l)
if [[ -n  ]]; then
    echo "Last line of file specified as non-opt/last argument:"
    tail -1 
fi
EOF

chmod +x /tmp/demo-equals-separated.sh

/tmp/demo-equals-separated.sh -e=conf -s=/etc -l=/usr/lib /etc/hosts
 


复制粘贴以上块的输出:

 FILE EXTENSION  = conf
SEARCH PATH     = /etc
LIBRARY PATH    = /usr/lib
DEFAULT         =
Number files in SEARCH PATH with EXTENSION: 14
Last line of file specified as non-opt/last argument:
#93.184.216.34    example.com
 


为了更好地理解${i#*=},请在本指南中搜索“子字符串删除”。它在功能上等效于`sed 's/[^=]*=//' <<< "$i"`(调用一个不必要的子进程)或`echo "$i" | sed 's/[^=]*=//'`(调用两个不必要的子进程)。

方法2:将bash与getopt [s]一起使用

来自:http://mywiki.wooledge.org/BashFAQ/035#getopts

getopt(1)的局限性(较旧的相对较新的getopt版本):


不能处理空字符串的参数
不能处理带有嵌入式空格的参数

更新的getopt版本没有这些限制。

另外,POSIX外壳(和其他)提供了getopts,但没有这些限制。我提供了一个简单的getopts示例。

用法demo-getopts.sh -vf /etc/hosts foo bar

 cat >/tmp/demo-getopts.sh <<'EOF'
#!/bin/sh

# A POSIX variable
OPTIND=1         # Reset in case getopts has been used previously in the shell.

# Initialize our own variables:
output_file=""
verbose=0

while getopts "h?vf:" opt; do
    case "$opt" in
    h|\?)
        show_help
        exit 0
        ;;
    v)  verbose=1
        ;;
    f)  output_file=$OPTARG
        ;;
    esac
done

shift $((OPTIND-1))

[ "${1:-}" = "--" ] && shift

echo "verbose=$verbose, output_file='$output_file', Leftovers: $@"
EOF

chmod +x /tmp/demo-getopts.sh

/tmp/demo-getopts.sh -vf /etc/hosts foo bar
 


复制粘贴到上面的块后得到的输出:

 verbose=1, output_file='/etc/hosts', Leftovers: foo bar
 


getopts的优点是:


它更便携,并且可以在其他外壳中使用,例如dash
它可以自动以典型的Unix方式处理多个选项,例如-vf filename

getopts的缺点在于,它只能处理简短的选项(-h,而不是--help),而无需附加代码。

有一个getopts教程,它解释了所有语法和变量的含义。在bash中,还有help getopts,这可能会提供信息。

评论


这是真的吗?根据Wikipedia的介绍,有一个较新的GNU增强版的getopt,其中包括getopts的所有功能,以及其中的一些功能。在Ubuntu 13.04上的man getopt输出getopt-解析命令选项(已增强)作为名称,因此我认为此增强版本现在是标准的。

–利文
13年6月6日在21:19

在您的系统上确定某件事是建立“成为标准”的前提的非常薄弱的​​前提。

– szablica
13年7月17日在15:23

@ Livven,getopt不是GNU实用程序,它是util-linux的一部分。

– Stephane Chazelas
2014年8月20日在19:55


您不回显–默认。在第一个示例中,我注意到,如果–default是最后一个参数,则除非将[[$$ -gt 1]]设置为while [[$#-gt 0],否则将不对其进行处理(视为非优化)。 ]]

–kolydart
17年7月10日在8:11



#2 楼



使用来自util-linux或以前的GNU glibc.1的增强型getopt

getopt_long() GNU glibc的C函数配合使用。
具有所有有用的区别功能(其他的则没有):

处理参数中的空格,引号甚至二进制文件(非增强型getopt不能这样做)
它可以在最后处理选项: script.sh -o outFile file1 file2 -vgetopts不这样做)
允许= -style长选项:script.sh --outfile=fileOut --infile fileIn(如果进行自我解析,则允许两者都很长)
允许组合短选项,例如-vfd(如果进行自我解析,则可以正常工作)
可以触摸选项参数,例如-oOutfile-vfdoOutfile
已经很老了3以至于没有GNU系统会缺少它(例如任何Linux都具有)。
您可以使用以下方法测试其存在: getopt --test→返回值4.
其他getopt或外壳内置的getopts用途有限。

以下调用
myscript -vfd ./foo/bar/someFile -o /fizz/someOtherFile
myscript -v -f -d -o/fizz/someOtherFile -- ./foo/bar/someFile
myscript --verbose --force --debug ./foo/bar/someFile -o/fizz/someOtherFile
myscript --output=/fizz/someOtherFile ./foo/bar/someFile -vfd
myscript ./foo/bar/someFile -df -v --output /fizz/someOtherFile

全部返回
verbose: y, force: y, debug: y, in: ./foo/bar/someFile, out: /fizz/someOtherFile

带有以下myscript
#!/bin/bash
# saner programming env: these switches turn some bugs into errors
set -o errexit -o pipefail -o noclobber -o nounset

# -allow a command to fail with !’s side effect on errexit
# -use return value from ${PIPESTATUS[0]}, because ! hosed $?
! getopt --test > /dev/null 
if [[ ${PIPESTATUS[0]} -ne 4 ]]; then
    echo 'I’m sorry, `getopt --test` failed in this environment.'
    exit 1
fi

OPTIONS=dfo:v
LONGOPTS=debug,force,output:,verbose

# -regarding ! and PIPESTATUS see above
# -temporarily store output to be able to check for errors
# -activate quoting/enhanced mode (e.g. by writing out “--options”)
# -pass arguments only via   -- "$@"   to separate them correctly
! PARSED=$(getopt --options=$OPTIONS --longoptions=$LONGOPTS --name "q4312078q" -- "$@")
if [[ ${PIPESTATUS[0]} -ne 0 ]]; then
    # e.g. return value is 1
    #  then getopt has complained about wrong arguments to stdout
    exit 2
fi
# read getopt’s output this way to handle the quoting right:
eval set -- "$PARSED"

d=n f=n v=n outFile=-
# now enjoy the options in order and nicely split until we see --
while true; do
    case "" in
        -d|--debug)
            d=y
            shift
            ;;
        -f|--force)
            f=y
            shift
            ;;
        -v|--verbose)
            v=y
            shift
            ;;
        -o|--output)
            outFile=""
            shift 2
            ;;
        --)
            shift
            break
            ;;
        *)
            echo "Programming error"
            exit 3
            ;;
    esac
done

# handle non-option arguments
if [[ $# -ne 1 ]]; then
    echo "q4312078q: A single input file is required."
    exit 4
fi

echo "verbose: $v, force: $f, debug: $d, in: , out: $outFile"


1增强的getopt在包括Cygwin在内的大多数“ bash系统”上可用。在OS X上,尝试执行brew install gnu-getopt或sudo port install getopt 2,POSIX exec()约定没有可靠的方法来在命令行参数中传递二进制NULL。这些字节过早地结束了1997年或更早发布的argument3第一版(我只追溯到1997年)

评论


谢谢你只需从en.wikipedia.org/wiki/Getopts上的功能表中进行确认,如果您需要对长选项的支持,并且您不在Solaris上,则可以使用getopt。

– johncip
17年1月12日在2:00

我相信,关于getopt的唯一警告是不能在包装脚本中方便地使用它,因为包装脚本中可能没有几个特定于包装脚本的选项,然后将non-wrapper-script选项完整地传递给包装的可执行文件。假设我有一个名为mygrep的grep包装器,并且有一个mygrep特有的--foo选项,那么我无法执行mygrep --foo -A 2,而-A 2自动传递给了grep;我需要做mygrep --foo --- A 2.这是我在解决方案之上的实现。

– Kaushal Modi
17-4-27在14:02



@bobpaul您关于util-linux的陈述也是错误的并且具有误导性:该软件包在Ubuntu / Debian上被标记为“必不可少”。因此,它总是被安装。 –您在谈论哪些发行版(您说需要专门安装在哪儿)?

– Robert Siemer
18年3月21日在9:16

请注意,至少在当前的10.14.3。上,此功能在Mac上不起作用。出厂的getopt是1999年的BSD getopt ...

– jjj
19年4月10日在13:12

@transang返回值的布尔取反。及其副作用:允许命令失败(否则errexit会在错误时中止程序)。 -脚本中的注释会告诉您更多信息。否则:男子重击

– Robert Siemer
19-11-29在2:04



#3 楼

更简洁的方法
deploy.sh
 #!/bin/bash

while [[ "$#" -gt 0 ]]; do
    case  in
        -t|--target) target=""; shift ;;
        -u|--uglify) uglify=1 ;;
        *) echo "Unknown parameter passed: "; exit 1 ;;
    esac
    shift
done

echo "Where to deploy: $target"
echo "Should uglify  : $uglify"
 

用法:
 ./deploy.sh -t dev -u

# OR:

./deploy.sh --target dev --uglify
 


评论


这就是我在做什么。如果要支持以布尔值标志结尾的行,则必须使用[[“ $#”> 1]]的时间。/script.sh--debug dev --uglify fast --verbose。示例:gist.github.com/hfossli/4368aa5a577742c3c9f9266ed214aa58

– hfossli
18年4月7日在20:58

哇!简单干净!这就是我的使用方式:gist.github.com/hfossli/4368aa5a577742c3c9f9266ed214aa58

– hfossli
18年4月7日在21:10

将其粘贴到每个脚本中要好得多,而不是与源代码打交道或让人们怀疑您的功能实际上从哪里开始。

– RealHandy
19年1月31日在20:05

警告:这允许重复的参数,以最新的参数为准。例如./script.sh -d dev -d prod将导致deploy =='prod'。我还是用它:P :):+1:

–yair
19-09-15在22:33



我正在使用这个(谢谢!),但请注意它允许空参数值,例如./script.sh -d不会产生错误,而只是将$ deploy设置为空字符串。

–EM0
1月12日7:26

#4 楼

来自:digitalpeer.com,进行了少量修改

用法 myscript.sh -p=my_prefix -s=dirname -l=libname

#!/bin/bash
for i in "$@"
do
case $i in
    -p=*|--prefix=*)
    PREFIX="${i#*=}"

    ;;
    -s=*|--searchpath=*)
    SEARCHPATH="${i#*=}"
    ;;
    -l=*|--lib=*)
    DIR="${i#*=}"
    ;;
    --default)
    DEFAULT=YES
    ;;
    *)
            # unknown option
    ;;
esac
done
echo PREFIX = ${PREFIX}
echo SEARCH PATH = ${SEARCHPATH}
echo DIRS = ${DIR}
echo DEFAULT = ${DEFAULT}


为了更好地理解${i#*=},请在本指南中搜索“子字符串删除”。它在功能上等效于调用一个不必要的子进程的`sed 's/[^=]*=//' <<< "$i"`或调用两个不必要的子进程的`echo "$i" | sed 's/[^=]*=//'`

评论


整齐!尽管这不适用于以空格分隔的自变量la mount -t tempfs...。人们可能可以通过while [$#-ge 1]之类的方法来解决此问题; do param = $ 1;转移;案例$ param in; -p)prefix = $ 1;转移;;等等

– Tobias Kienzler
13年11月12日在12:48



这无法处理-vfd样式组合的简短选项。

– Robert Siemer
16 Mar 19 '16 at 15:23

#5 楼

getopt() / getopts()是一个不错的选择。在此微型脚本中显示了从此处被盗的信息:


简单使用“ getopt”:


#!/bin/bash
echo "Before getopt"
for i
do
  echo $i
done
args=`getopt abc:d $*`
set -- $args
echo "After getopt"
for i
do
  echo "-->$i"
done



我们所说的是-a,
-b,-c或-d中的任何一个都将被允许,但是-c后跟一个参数(“ c:”表示)。

如果我们称它为“ g”并尝试一下:


bash-2.05a$ ./g -abc foo
Before getopt
-abc
foo
After getopt
-->-a
-->-b
-->-c
-->foo
-->--



我们从两个参数,并且
“ getopt”分解选项,并且
将每个参数置于其自己的参数中。它还
添加了“-”。


评论


使用$ *破坏了getopt的用法。 (它使参数带有空格。)请参阅我的回答以获取正确用法。

– Robert Siemer
16年4月16日在14:37

为什么要使其变得更复杂?

– SDsolar
17年8月10日在14:07

@Matt J,如果使用“ $ i”而不是$ i,脚本的第一部分(对于i)将能够处理其中带有空格的参数。 getopts似乎无法处理带空格的参数。在for i循环上使用getopt的好处是什么?

–thebunnyrules
18年6月1日在1:57

#6 楼

冒着添加另一个示例忽略的风险,这是我的方案。


处理-n arg--name=arg

最后允许参数如果任何内容拼写错误
兼容,不使用bashisms
可读,不需要循环维护状态

希望对某人有用。

 while [ "$#" -gt 0 ]; do
  case "" in
    -n) name=""; shift 2;;
    -p) pidfile=""; shift 2;;
    -l) logfile=""; shift 2;;

    --name=*) name="${1#*=}"; shift 1;;
    --pidfile=*) pidfile="${1#*=}"; shift 1;;
    --logfile=*) logfile="${1#*=}"; shift 1;;
    --name|--pidfile|--logfile) echo " requires an argument" >&2; exit 1;;

    -*) echo "unknown option: " >&2; exit 1;;
    *) handle_argument ""; shift 1;;
  esac
done
 


评论


抱歉耽搁了。在我的脚本中,handle_argument函数接收所有非选项参数。您可以将其替换为所需的行,也许*)die“无法识别的参数:$ 1”或将args收集到变量中*)args + =“ $ 1”;移1 ;;

–布朗森
2015年10月8日在20:41



惊人!我已经测试了几个答案,但这是唯一适用于所有情况的答案,包括许多位置参数(标志之前和之后)

– Guilherme Garnier
18年4月13日在16:10

简洁的代码,但使用-n且不使用其他arg会由于移位2上的错误而导致无限循环,发出两次移位而不是移位2。建议进行编辑。

– Lauksas
19-4-27的23:22

我进行了编辑(正在等待审阅),以添加一些有用的功能,同时保持代码简单明了。对于更高级的功能,例如单个参数中有多个一个字母的选项,应尝试使用getopt或getopts。

–leogama
6月27日1:28

#7 楼

我这个问题迟到了4年,但想退回给我。我使用较早的答案作为整理我的旧即席参数解析的起点。然后,我重构了以下模板代码。它使用=或空格分隔的参数以及组合在一起的多个短参数处理长参数和短参数。最后,它将所有非参数参数重新插入到$ 1,$ 2 ..变量中。我希望它有用。

#!/usr/bin/env bash

# NOTICE: Uncomment if your script depends on bashisms.
#if [ -z "$BASH_VERSION" ]; then bash q4312078q $@ ; exit $? ; fi

echo "Before"
for i ; do echo - $i ; done


# Code template for parsing command line parameters using only portable shell
# code, while handling both long and short params, handling '-f file' and
# '-f=file' style param data and also capturing non-parameters to be inserted
# back into the shell positional parameters.

while [ -n "" ]; do
        # Copy so we can modify it (can't modify )
        OPT=""
        # Detect argument termination
        if [ x"$OPT" = x"--" ]; then
                shift
                for OPT ; do
                        REMAINS="$REMAINS \"$OPT\""
                done
                break
        fi
        # Parse current opt
        while [ x"$OPT" != x"-" ] ; do
                case "$OPT" in
                        # Handle --flag=value opts like this
                        -c=* | --config=* )
                                CONFIGFILE="${OPT#*=}"
                                shift
                                ;;
                        # and --flag value opts like this
                        -c* | --config )
                                CONFIGFILE=""
                                shift
                                ;;
                        -f* | --force )
                                FORCE=true
                                ;;
                        -r* | --retry )
                                RETRY=true
                                ;;
                        # Anything unknown is recorded for later
                        * )
                                REMAINS="$REMAINS \"$OPT\""
                                break
                                ;;
                esac
                # Check for multiple short options
                # NOTICE: be sure to update this pattern to match valid options
                NEXTOPT="${OPT#-[cfr]}" # try removing single short opt
                if [ x"$OPT" != x"$NEXTOPT" ] ; then
                        OPT="-$NEXTOPT"  # multiple short opts, keep going
                else
                        break  # long form, exit inner loop
                fi
        done
        # Done with that param. move to next
        shift
done
# Set the non-parameters back into the positional parameters (  ..)
eval set -- $REMAINS


echo -e "After: \n configfile='$CONFIGFILE' \n force='$FORCE' \n retry='$RETRY' \n remains='$REMAINS'"
for i ; do echo - $i ; done


评论


此代码无法处理带有以下参数的选项:-c1。使用=将短选项与其参数分开是不寻常的...

– Robert Siemer
2015年12月6日下午13:47

我在使用这段有用的代码时遇到了两个问题:1)在“ -c = foo”的情况下,“ shift”最终占用了下一个参数;和2)对于组合的简短期权,不应在“ [cfr]”模式中包含“ c”。

–sfnd
16年6月6日在19:28

#8 楼

我发现在脚本中编写可移植的解析非常令人沮丧,以至于我写了Argbash-一个FOSS代码生成器,它可以为您的脚本生成参数解析代码,并且具有一些不错的功能:

https ://argbash.io

评论


感谢您编写argbash,我刚刚使用它,发现它运行良好。我主要选择argbash,因为它是支持OS X 10.11 El Capitan上较旧的bash 3.x的代码生成器。唯一的缺点是,与调用模块相比,代码生成器方法在您的主脚本中意味着大量代码。

– RichVel
16年8月18日在5:34

实际上,您可以使用Argbash,它可以为您生成量身定制的解析库,您可以将其包含在脚本中,也可以将其保存在单独的文件中并仅提供源代码。我添加了一个示例来说明这一点,并且在文档中也做了更明确的说明。

– bubla
16年8月23日在20:40



很高兴知道。该示例很有趣,但仍不太清楚-也许您可以将生成的脚本的名称更改为'parse_lib.sh'或类似名称,并显示主脚本在何处调用它(例如在更复杂的用例中的包装脚本部分)。

– RichVel
16年8月24日在5:47

在argbash的最新版本中解决了这些问题:文档得到了改进,引入了快速入门argbash-init脚本,您甚至可以在argbash.io/generate上在线使用argbash。

– bubla
16年2月2日在20:12

#9 楼

我的答案主要基于Bruno Bronosky的答案,但我将他的两个纯bash实现混搭为一个我经常使用的实现。

# As long as there is at least one more argument, keep looping
while [[ $# -gt 0 ]]; do
    key=""
    case "$key" in
        # This is a flag type option. Will catch either -f or --foo
        -f|--foo)
        FOO=1
        ;;
        # Also a flag type option. Will catch either -b or --bar
        -b|--bar)
        BAR=1
        ;;
        # This is an arg value type option. Will catch -o value or --output-file value
        -o|--output-file)
        shift # past the key and to the value
        OUTPUTFILE=""
        ;;
        # This is an arg=value type option. Will catch -o=value or --output-file=value
        -o=*|--output-file=*)
        # No need to shift here since the value is part of the same string
        OUTPUTFILE="${key#*=}"
        ;;
        *)
        # Do whatever you want with extra options
        echo "Unknown option '$key'"
        ;;
    esac
    # Shift after checking all the cases to get the next option
    shift
done


同时具有空格分隔的选项/值和相等的定义值。

因此可以使用以下脚本运行脚本:

./myscript --foo -b -o /fizz/file.txt


以及:

./myscript -f --bar -o=/fizz/file.txt


,并且两者应具有相同的最终结果。

PROS:


>同时允许-arg = value和-arg值

使用可在bash中使用的任何arg名称进行工作


含义-a或-arg或--arg或-arg或其他


纯bash。无需学习/使用getopt或getopts

缺点:



不能组合参数


的意思是-abc。您必须-a -b -c



这些是我能想到的唯一优点/缺点

#10 楼

本示例说明如何使用getopteval以及HEREDOCshift来处理带有和不带有后续必需值的短和长参数。 switch / case语句也简洁明了,易于遵循。
#!/usr/bin/env bash

# usage function
function usage()
{
   cat << HEREDOC

   Usage: $progname [--num NUM] [--time TIME_STR] [--verbose] [--dry-run]

   optional arguments:
     -h, --help           show this help message and exit
     -n, --num NUM        pass in a number
     -t, --time TIME_STR  pass in a time string
     -v, --verbose        increase the verbosity of the bash script
     --dry-run            do a dry run, dont change any files

HEREDOC
}  

# initialize variables
progname=$(basename 
OPTS=$(getopt -o "hn:t:v" --long "help,num:,time:,verbose,dry-run" -n "$progname" -- "$@")
if [ $? != 0 ] ; then echo "Error in command line arguments." >&2 ; exit 1 ; fi
eval set -- "$OPTS"

while true; do
  case "" in
    -h | --help ) usage; exit; ;;
    -n | --num ) num_str=""; shift 2 ;;
    -t | --time ) time_str=""; shift 2 ;;
    --dry-run ) dryrun=1; shift ;;
    -v | --verbose ) verbose=$((verbose + 1)); shift ;;
    -- ) shift; break ;;
    * ) break ;;
  esac
done
) verbose=0 dryrun=0 num_str= time_str= # use getopt and store the output into $OPTS # note the use of -o for the short options, --long for the long name options # and a : for any option that takes a parameter OPTS=$(getopt -o "hn:t:v" --long "help,num:,time:,verbose,dry-run" -n "$progname" -- "$@") if [ $? != 0 ] ; then echo "Error in command line arguments." >&2 ; usage; exit 1 ; fi eval set -- "$OPTS" while true; do # uncomment the next line to see how shift is working # echo "$1:\"\" $2:\"\"" case "" in -h | --help ) usage; exit; ;; -n | --num ) num_str=""; shift 2 ;; -t | --time ) time_str=""; shift 2 ;; --dry-run ) dryrun=1; shift ;; -v | --verbose ) verbose=$((verbose + 1)); shift ;; -- ) shift; break ;; * ) break ;; esac done if (( $verbose > 0 )); then # print out all the parameters we read in cat <<EOM num=$num_str time=$time_str verbose=$verbose dryrun=$dryrun EOM fi # The rest of your script below

上面脚本中最重要的几行是:
q4312078q
简而言之,可读性强,并处理几乎所有内容(IMHO)。
希望对某人有所帮助。

评论


这是最好的答案之一。

– Polywhirl先生
3月5日12:30

#11 楼

扩展@guneysus的出色答案,这是一项调整,使用户可以使用他们喜欢的任何语法,例如

command -x=myfilename.ext --another_switch 


vs

command -x myfilename.ext --another_switch


也就是说,等于可以用空格代替。

这种“模糊的解释”可能并不符合您的喜好,但是如果您要编写可与其他实用程序互换的脚本(像我的情况那样,必须与ffmpeg一起使用),则灵活性是有用。

STD_IN=0

prefix=""
key=""
value=""
for keyValue in "$@"
do
  case "${prefix}${keyValue}" in
    -i=*|--input_filename=*)  key="-i";     value="${keyValue#*=}";; 
    -ss=*|--seek_from=*)      key="-ss";    value="${keyValue#*=}";;
    -t=*|--play_seconds=*)    key="-t";     value="${keyValue#*=}";;
    -|--stdin)                key="-";      value=1;;
    *)                                      value=$keyValue;;
  esac
  case $key in
    -i) MOVIE=$(resolveMovie "${value}");  prefix=""; key="";;
    -ss) SEEK_FROM="${value}";          prefix=""; key="";;
    -t)  PLAY_SECONDS="${value}";           prefix=""; key="";;
    -)   STD_IN=${value};                   prefix=""; key="";; 
    *)   prefix="${keyValue}=";;
  esac
done


#12 楼

我认为使用起来非常简单:

#!/bin/bash
#

readopt='getopts $opts opt;rc=$?;[ $rc$opt == 0? ]&&exit 1;[ $rc == 0 ]||{ shift $[OPTIND-1];false; }'

opts=vfdo:

# Enumerating options
while eval $readopt
do
    echo OPT:$opt ${OPTARG+OPTARG:$OPTARG}
done

# Enumerating arguments
for arg
do
    echo ARG:$arg
done


调用示例:

./myscript -v -do /fizz/someOtherFile -f ./foo/bar/someFile
OPT:v 
OPT:d 
OPT:o OPTARG:/fizz/someOtherFile
OPT:f 
ARG:./foo/bar/someFile


评论


我读了所有,这是我的首选。我不喜欢使用-a = 1作为argc样式。我更喜欢先放置主选项-options,然后放置特殊的单间距-o选项。我正在寻找最简单的vs更好的方式来读取argvs。

– m3nda
15年5月20日在22:50

它确实运行良好,但是如果您将参数传递给非a:选项,则以下所有选项都将用作参数。您可以使用自己的脚本检查此行./myscript -v -d fail -o / fizz / someOtherFile -f ./foo/bar/someFile。 -d选项未设置为d:

– m3nda
2015年5月20日23:25

#13 楼

EasyOptions不需要任何解析:

## Options:
##   --verbose, -v  Verbose mode
##   --output=FILE  Output filename

source easyoptions || exit

if test -n "${verbose}"; then
    echo "output file is ${output}"
    echo "${arguments[@]}"
fi


评论


我花了一分钟的时间意识到示例脚本顶部的注释已被解析,以提供默认的用法帮助字符串以及参数说明。这是一个绝妙的解决方案,对不起,它在2年内只获得6票。也许这个问题已经淹没了,人们无法注意到。

–变形
18-09-9在7:44

从某种意义上说,您的解决方案是迄今为止最好的解决方案(除了@OleksiiChekulaiev的解决方案,该解决方案不支持“标准”选项语法)。这是因为您的解决方案仅需要脚本编写者一次指定每个选项的名称。其他解决方案要求将其指定3次(在用法,“案例”模式下以及在变量的设置中),这一事实一直困扰着我。甚至getopt也有此问题。但是,您的代码在我的计算机上运行缓慢-Bash实现为0.11s,Ruby为0.28s。与0.02s进行显式的“ while-case”解析。

–变形
18-09-9在7:51



我想要一个更快的版本,也许是用C编写的。而且,这个版本与zsh兼容。也许这值得一个单独的问题(“是否有一种方法可以在类似Bash的shell中解析命令行参数,它接受标准的长选项语法,并且不需要多次键入选项名称?”)。

–变形
18年9月9日在8:04

#14 楼

我给你的功能parse_params可以从命令行解析参数。


这是一个纯Bash解决方案,没有其他实用程序。
不会污染全局范围。
毫不费力地返回给您简单易用的变量,您可以在其上进一步构建逻辑。
参数前的破折号无关紧要(--all等于-all等于all=all

下面的脚本是复制粘贴的工作演示。请参阅show_use函数以了解如何使用parse_params

限制:


不支持以空格分隔的参数(-d 1
参数名称将丢失破折号因此,--any-param-anyparam是等效的

eval $(parse_params "$@")必须在bash函数内部使用(在全局范围内不起作用)


#!/bin/bash

# Universal Bash parameter parsing
# Parse equal sign separated params into named local variables
# Standalone named parameter value will equal its param name (--force creates variable $force=="force")
# Parses multi-valued named params into an array (--path=path1 --path=path2 creates ${path[*]} array)
# Puts un-named params as-is into ${ARGV[*]} array
# Additionally puts all named params as-is into ${ARGN[*]} array
# Additionally puts all standalone "option" params as-is into ${ARGO[*]} array
# @author Oleksii Chekulaiev
# @version v1.4.1 (Jul-27-2018)
parse_params ()
{
    local existing_named
    local ARGV=() # un-named params
    local ARGN=() # named params
    local ARGO=() # options (--params)
    echo "local ARGV=(); local ARGN=(); local ARGO=();"
    while [[ "" != "" ]]; do
        # Escape asterisk to prevent bash asterisk expansion, and quotes to prevent string breakage
        _escaped=${1/\*/\'\"*\"\'}
        _escaped=${_escaped//\'/\\'}
        _escaped=${_escaped//\"/\\"}
        # If equals delimited named parameter
        nonspace="[^[:space:]]"
        if [[ "" =~ ^${nonspace}${nonspace}*=..* ]]; then
            # Add to named parameters array
            echo "ARGN+=('$_escaped');"
            # key is part before first =
            local _key=$(echo "" | cut -d = -f 1)
            # Just add as non-named when key is empty or contains space
            if [[ "$_key" == "" || "$_key" =~ " " ]]; then
                echo "ARGV+=('$_escaped');"
                shift
                continue
            fi
            # val is everything after key and = (protect from param==value error)
            local _val="${1/$_key=}"
            # remove dashes from key name
            _key=${_key//\-}
            # skip when key is empty
            # search for existing parameter name
            if (echo "$existing_named" | grep "\b$_key\b" >/dev/null); then
                # if name already exists then it's a multi-value named parameter
                # re-declare it as an array if needed
                if ! (declare -p _key 2> /dev/null | grep -q 'declare \-a'); then
                    echo "$_key=(\"$$_key\");"
                fi
                # append new value
                echo "$_key+=('$_val');"
            else
                # single-value named parameter
                echo "local $_key='$_val';"
                existing_named=" $_key"
            fi
        # If standalone named parameter
        elif [[ "" =~ ^\-${nonspace}+ ]]; then
            # remove dashes
            local _key=${1//\-}
            # Just add as non-named when key is empty or contains space
            if [[ "$_key" == "" || "$_key" =~ " " ]]; then
                echo "ARGV+=('$_escaped');"
                shift
                continue
            fi
            # Add to options array
            echo "ARGO+=('$_escaped');"
            echo "local $_key=\"$_key\";"
        # non-named parameter
        else
            # Escape asterisk to prevent bash asterisk expansion
            _escaped=${1/\*/\'\"*\"\'}
            echo "ARGV+=('$_escaped');"
        fi
        shift
    done
}

#--------------------------- DEMO OF THE USAGE -------------------------------

show_use ()
{
    eval $(parse_params "$@")
    # --
    echo "${ARGV[0]}" # print first unnamed param
    echo "${ARGV[1]}" # print second unnamed param
    echo "${ARGN[0]}" # print first named param
    echo "${ARG0[0]}" # print first option param (--force)
    echo "$anyparam"  # print --anyparam value
    echo "$k"         # print k=5 value
    echo "${multivalue[0]}" # print first value of multi-value
    echo "${multivalue[1]}" # print second value of multi-value
    [[ "$force" == "force" ]] && echo "$force is set so let the force be with you"
}

show_use "param 1" --anyparam="my value" param2 k=5 --force --multi-value=test1 --multi-value=test2


评论


要使用演示来解析bash脚本中包含的参数,只需执行show_use“ $ @”

– Oleksii Chekulaiev
16-9-28在12:55

基本上,我发现github.com/renatosilva/easyoptions以相同的方式执行相同的操作,但是比该功能要大得多。

– Oleksii Chekulaiev
16-09-28在12:58



#15 楼

如果安装了#1,并且打算在同一平台上运行#2,则getopts效果很好。例如,OSX和Linux在这方面的行为有所不同。

这是(非getopts)解决方案,它支持equals,non-equals和boolean标志。例如,您可以通过以下方式运行脚本:

./script --arg1=value1 --arg2 value2 --shouldClean

# parse the arguments.
COUNTER=0
ARGS=("$@")
while [ $COUNTER -lt $# ]
do
    arg=${ARGS[$COUNTER]}
    let COUNTER=COUNTER+1
    nextArg=${ARGS[$COUNTER]}

    if [[ $skipNext -eq 1 ]]; then
        echo "Skipping"
        skipNext=0
        continue
    fi

    argKey=""
    argVal=""
    if [[ "$arg" =~ ^\- ]]; then
        # if the format is: -key=value
        if [[ "$arg" =~ \= ]]; then
            argVal=$(echo "$arg" | cut -d'=' -f2)
            argKey=$(echo "$arg" | cut -d'=' -f1)
            skipNext=0

        # if the format is: -key value
        elif [[ ! "$nextArg" =~ ^\- ]]; then
            argKey="$arg"
            argVal="$nextArg"
            skipNext=1

        # if the format is: -key (a boolean flag)
        elif [[ "$nextArg" =~ ^\- ]] || [[ -z "$nextArg" ]]; then
            argKey="$arg"
            argVal=""
            skipNext=0
        fi
    # if the format has not flag, just a value.
    else
        argKey=""
        argVal="$arg"
        skipNext=0
    fi

    case "$argKey" in 
        --source-scmurl)
            SOURCE_URL="$argVal"
        ;;
        --dest-scmurl)
            DEST_URL="$argVal"
        ;;
        --version-num)
            VERSION_NUM="$argVal"
        ;;
        -c|--clean)
            CLEAN_BEFORE_START="1"
        ;;
        -h|--help|-help|--h)
            showUsage
            exit
        ;;
    esac
done


#16 楼

在扩展@ bruno-bronosky的答案时,我添加了一个“预处理器”来处理一些常见的格式:


--longopt=val扩展到--longopt val

-xyz扩展到-x -y -z

支持--指示标志的结尾
为意外选项显示错误
紧凑易读的选项开关

 #!/bin/bash

# Report usage
usage() {
  echo "Usage:"
  echo "$(basename q4312079q) [options] [--] [file1, ...]"

  # Optionally exit with a status code
  if [ -n "" ]; then
    exit ""
  fi
}

invalid() {
  echo "ERROR: Unrecognized argument: " >&2
  usage 1
}

# Pre-process options to:
# - expand -xyz into -x -y -z
# - expand --longopt=arg into --longopt arg
ARGV=()
END_OF_OPT=
while [[ $# -gt 0 ]]; do
  arg=""; shift
  case "${END_OF_OPT}${arg}" in
    --) ARGV+=("$arg"); END_OF_OPT=1 ;;
    --*=*)ARGV+=("${arg%%=*}" "${arg#*=}") ;;
    --*) ARGV+=("$arg"); END_OF_OPT=1 ;;
    -*) for i in $(seq 2 ${#arg}); do ARGV+=("-${arg:i-1:1}"); done ;;
    *) ARGV+=("$arg") ;;
  esac
done

# Apply pre-processed options
set -- "${ARGV[@]}"

# Parse options
END_OF_OPT=
POSITIONAL=()
while [[ $# -gt 0 ]]; do
  case "${END_OF_OPT}" in
    -h|--help)      usage 0 ;;
    -p|--password)  shift; PASSWORD="" ;;
    -u|--username)  shift; USERNAME="" ;;
    -n|--name)      shift; names+=("") ;;
    -q|--quiet)     QUIET=1 ;;
    -C|--copy)      COPY=1 ;;
    -N|--notify)    NOTIFY=1 ;;
    --stdin)        READ_STDIN=1 ;;
    --)             END_OF_OPT=1 ;;
    -*)             invalid "" ;;
    *)              POSITIONAL+=("") ;;
  esac
  shift
done

# Restore positional parameters
set -- "${POSITIONAL[@]}"
 


#17 楼

这是我在函数中所做的操作,以避免破坏同时在堆栈中更高位置处运行的getopts:

function waitForWeb () {
   local OPTIND=1 OPTARG OPTION
   local host=localhost port=8080 proto=http
   while getopts "h:p:r:" OPTION; do
      case "$OPTION" in
      h)
         host="$OPTARG"
         ;;
      p)
         port="$OPTARG"
         ;;
      r)
         proto="$OPTARG"
         ;;
      esac
   done
...
}


#18 楼

我想提交我的项目:https://github.com/flyingangel/argparser

source argparser.sh
parse_args "$@"


就这么简单。环境中将填充与参数名称相同的变量

#19 楼

有几种解析cmdline args的方法(例如,GNU getopt(不可移植)与BSD(OSX)getopt与getopts)-都是有问题的。此解决方案是


便携式!
零依赖,仅依赖bash内置
允许短和长选项
处理之间的空白选项和参数,但也可以使用=分隔符
支持串联的简短选项样式-vxf

带有可选参数的句柄选项(请参见示例),并且
与相同功能集的替代方案。即简洁,因此更易于维护

示例:任何

# flag
-f
--foo

# option with required argument
-b"Hello World"
-b "Hello World"
--bar "Hello World"
--bar="Hello World"

# option with optional argument
--baz
--baz="Optional Hello"



#!/usr/bin/env bash

usage() {
  cat - >&2 <<EOF
NAME
    program-name.sh - Brief description

SYNOPSIS
    program-name.sh [-h|--help]
    program-name.sh [-f|--foo]
                    [-b|--bar <arg>]
                    [--baz[=<arg>]]
                    [--]
                    FILE ...

REQUIRED ARGUMENTS
  FILE ...
          input files

OPTIONS
  -h, --help
          Prints this and exits

  -f, --foo
          A flag option

  -b, --bar <arg>
          Option requiring an argument <arg>

  --baz[=<arg>]
          Option that has an optional argument <arg>. If <arg>
          is not specified, defaults to 'DEFAULT'
  --     
          Specify end of options; useful if the first non option
          argument starts with a hyphen

EOF
}

fatal() {
    for i; do
        echo -e "${i}" >&2
    done
    exit 1
}

# For long option processing
next_arg() {
    if [[ $OPTARG == *=* ]]; then
        # for cases like '--opt=arg'
        OPTARG="${OPTARG#*=}"
    else
        # for cases like '--opt arg'
        OPTARG="${args[$OPTIND]}"
        OPTIND=$((OPTIND + 1))
    fi
}

# ':' means preceding option character expects one argument, except
# first ':' which make getopts run in silent mode. We handle errors with
# wildcard case catch. Long options are considered as the '-' character
optspec=":hfb:-:"
args=("" "$@")  # dummy first element so  and $args[1] are aligned
while getopts "$optspec" optchar; do
    case "$optchar" in
        h) usage; exit 0 ;;
        f) foo=1 ;;
        b) bar="$OPTARG" ;;
        -) # long option processing
            case "$OPTARG" in
                help)
                    usage; exit 0 ;;
                foo)
                    foo=1 ;;
                bar|bar=*) next_arg
                    bar="$OPTARG" ;;
                baz)
                    baz=DEFAULT ;;
                baz=*) next_arg
                    baz="$OPTARG" ;;
                -) break ;;
                *) fatal "Unknown option '--${OPTARG}'" "see 'q4312078q --help' for usage" ;;
            esac
            ;;
        *) fatal "Unknown option: '-${OPTARG}'" "See 'q4312078q --help' for usage" ;;
    esac
done

shift $((OPTIND-1))

if [ "$#" -eq 0 ]; then
    fatal "Expected at least one required argument FILE" \
    "See 'q4312078q --help' for usage"
fi

echo "foo=$foo, bar=$bar, baz=$baz, files=${@}"


#20 楼

我想提供我的选项分析版本,该版本允许以下操作:

-s p1
--stage p1
-w somefolder
--workfolder somefolder
-sw p1 somefolder
-e=hello


也允许此操作(可能是不需要的):

-s--workfolder p1 somefolder
-se=hello p1
-swe=hello p1 somefolder


必须在使用前决定是否在选项上使用=。这是为了保持代码整洁。

while [[ $# > 0 ]]
do
    key=""
    while [[ ${key+x} ]]
    do
        case $key in
            -s*|--stage)
                STAGE=""
                shift # option has parameter
                ;;
            -w*|--workfolder)
                workfolder=""
                shift # option has parameter
                ;;
            -e=*)
                EXAMPLE="${key#*=}"
                break # option has been fully handled
                ;;
            *)
                # unknown option
                echo Unknown option: $key #1>&2
                exit 10 # either this: my preferred way to handle unknown options
                break # or this: do this to signal the option has been handled (if exit isn't used)
                ;;
        esac
        # prepare for next option in this key, if any
        [[ "$key" = -? || "$key" == --* ]] && unset key || key="${key/#-?/-}"
    done
    shift # option(s) fully processed, proceed to next input argument
done


评论


$ {key + x}上的“ + x”是什么意思?

–路卡·达万佐(Luca Davanzo)
16年11月14日在17:56

这是一项测试,看是否存在“密钥”。再往下,我取消设置键,这会破坏内部的while循环。

– Galmok
16年11月15日在9:10

#21 楼

保留未处理参数的解决方案。包括演示。

这是我的解决方案。它非常灵活,与其他应用程序不同,它不需要外部程序包并干净地处理剩余的参数。

用法是:./myscript -flag flagvariable -otherflag flagvar2

您要做的就是编辑有效标志行。它加上一个连字符并搜索所有参数。然后,它将下一个参数定义为标志名称,例如:

./myscript -flag flagvariable -otherflag flagvar2
echo $flag $otherflag
flagvariable flagvar2


主代码(简短版本,详细示例如下,还有出错的版本):

#!/usr/bin/env bash
#shebang.io
validflags="rate time number"
count=1
for arg in $@
do
    match=0
    argval=
    for flag in $validflags
    do
        sflag="-"$flag
        if [ "$argval" == "$sflag" ]
        then
            declare $flag=
            match=1
        fi
    done
        if [ "$match" == "1" ]
    then
        shift 2
    else
        leftovers=$(echo $leftovers $argval)
        shift
    fi
    count=$(($count+1))
done
#Cleanup then restore the leftovers
shift $#
set -- $leftovers


内置回显演示的详细版本:

#!/usr/bin/env bash
#shebang.io
rate=30
time=30
number=30
echo "all args
$@"
validflags="rate time number"
count=1
for arg in $@
do
    match=0
    argval=
#   argval=$(echo $@ | cut -d ' ' -f$count)
    for flag in $validflags
    do
            sflag="-"$flag
        if [ "$argval" == "$sflag" ]
        then
            declare $flag=
            match=1
        fi
    done
        if [ "$match" == "1" ]
    then
        shift 2
    else
        leftovers=$(echo $leftovers $argval)
        shift
    fi
    count=$(($count+1))
done

#Cleanup then restore the leftovers
echo "pre final clear args:
$@"
shift $#
echo "post final clear args:
$@"
set -- $leftovers
echo "all post set args:
$@"
echo arg1:  arg2: 

echo leftovers: $leftovers
echo rate $rate time $time number $number


最后一个,如果出现此错误传递了无效的参数。

#!/usr/bin/env bash
#shebang.io
rate=30
time=30
number=30
validflags="rate time number"
count=1
for arg in $@
do
    argval=
    match=0
        if [ "${argval:0:1}" == "-" ]
    then
        for flag in $validflags
        do
                sflag="-"$flag
            if [ "$argval" == "$sflag" ]
            then
                declare $flag=
                match=1
            fi
        done
        if [ "$match" == "0" ]
        then
            echo "Bad argument: $argval"
            exit 1
        fi
        shift 2
    else
        leftovers=$(echo $leftovers $argval)
        shift
    fi
    count=$(($count+1))
done
#Cleanup then restore the leftovers
shift $#
set -- $leftovers
echo rate $rate time $time number $number
echo leftovers: $leftovers


缺点:无法解析单个复杂的arg字符串,例如-xcvf将作为单个参数处理。您可以轻松地向我的代码中编写其他代码,以添加此功能。

#22 楼

另一个选项解析器(生成器)
https://github.com/ko1nksm/getoptions
getoptions是一种新的选项解析器(生成器),它是用符合POSIX的shell脚本编写的,于2020年8月发布。对于那些希望在shell脚本中支持标准选项语法而又不加重批评的人。支持的语法是-a+a-abc-vvv-p VALUE-pVALUE--flag--no-flag--param VALUE--param=VALUE--option[=VALUE]--no-option --
它支持自动生成子命令,验证和ab。并适用于大多数操作系统(Linux,macOS,BSD,Windows等)和所有POSIX Shell(破折号,bash,ksh,zsh等)。
 #!/bin/sh

. ./lib/getoptions.sh
. ./lib/getoptions_help.sh

parser_definition() {
  setup   REST help:usage -- "Usage: ${2##*/} [options] [arguments]" ''
  flag    FLAG    -f --flag                      -- "--flag option"
  param   PARAM   -p --param                     -- "--param option"
  option  OPTION  -o --option on:"default"  -- "--option option"
  disp    :usage  -h --help
  disp    VERSION    --version
}

eval "$(getoptions parser_definition parse "FLAG=''
PARAM=''
OPTION=''
REST=''
parse() {
  OPTIND=$(($#+1))
  while OPTARG= && [ $# -gt 0 ]; do
    case  in
      --?*=*) OPTARG=; shift
        eval 'set -- "${OPTARG%%\=*}" "${OPTARG#*\=}"' ${1+'"$@"'}
        ;;
      --no-*) unset OPTARG ;;
      -[po]?*) OPTARG=; shift
        eval 'set -- "${OPTARG%"${OPTARG#??}"}" "${OPTARG#??}"' ${1+'"$@"'}
        ;;
      -[!-]?*) OPTARG=; shift
        eval 'set -- "${OPTARG%"${OPTARG#??}"}" "-${OPTARG#??}"' ${1+'"$@"'}
        OPTARG= ;;
    esac
    case  in
      -f | --flag)
        [ "${OPTARG:-}" ] && OPTARG=${OPTARG#*\=} && set -- noarg "" && break
        eval '[ ${OPTARG+x} ] &&:' && OPTARG='1' || OPTARG=''
        FLAG="$OPTARG"
        ;;
      -p | --param)
        [ $# -le 1 ] && set -- required "" && break
        OPTARG=
        PARAM="$OPTARG"
        shift ;;
      -o | --option)
        set -- "" "$@"
        [ ${OPTARG+x} ] && {
          case  in --no-*) set -- noarg "${1%%\=*}"; break; esac
          [ "${OPTARG:-}" ] && { shift; OPTARG=; } || OPTARG='default'
        } || OPTARG=''
        OPTION="$OPTARG"
        shift ;;
      -h | --help)
        usage
        exit 0 ;;
      --version)
        echo "${VERSION}"
        exit 0 ;;
      --) shift
        while [ $# -gt 0 ]; do
          REST="${REST} \"${$((${OPTIND:-0}-$#))}\""
          shift
        done
        break ;;
      [-]?*) set -- unknown "" && break ;;
      *) REST="${REST} \"${$((${OPTIND:-0}-$#))}\""
    esac
    shift
  done
  [ $# -eq 0 ] && { OPTIND=1; unset OPTARG; return 0; }
  case  in
    unknown) set -- "Unrecognized option: " "$@" ;;
    noarg) set -- "Does not allow an argument: " "$@" ;;
    required) set -- "Requires an argument: " "$@" ;;
    pattern:*) set -- "Does not match the pattern (${1#*:}): " "$@" ;;
    *) set -- "Validation error (): " "$@"
  esac
  echo "" >&2
  exit 1
}
usage() {
cat<<'GETOPTIONSHERE'
Usage: example.sh [options] [arguments]

  -f, --flag                  --flag option
  -p, --param PARAM           --param option
  -o, --option[=OPTION]       --option option
  -h, --help                  
      --version               
GETOPTIONSHERE
}
")"
parse "$@"
eval "set -- $REST"

echo "FLAG: $FLAG"
echo "PARAM: $PARAM"
echo "OPTION: $OPTION"
printf ': %s\n' "$@" # Rest arguments
 

它也是一个选项解析器生成器,生成以下选项解析代码。
 q4312079q 


#23 楼

请注意,getopt(1)是AT&T的一个短暂的错误。

getopt于1984年创建,但由于它实际上不可用而于1986年被掩埋。

事实证明getopt非常过时,因为getopt(1)手册页仍然提到"$*"而不是"$@",1986年它与内置的getopts(1) shell一起添加到了Bourne Shell中,以处理内部带有空格的参数。

顺便说一句:如果您有兴趣在shell脚本中解析长选项,可能想知道libc(Solaris)的getopt(3)实现和ksh93都添加了统一的长选项实现,该实现支持将长选项作为短选项的别名。这导致ksh93Bourne Shell通过getopts为长选项实现统一的接口。

从Bourne Shell手册页中获取长选项的示例:

getopts "f:(file)(input-file)o:(output-file)" OPTX "$@"

显示在Bourne Shell和ksh93中可以使用多长时间的选项别名。

请参阅最新的Bourne Shell的手册页:

http:// schillix.sourceforge.net/man/man1/bosh.1.html

,以及来自OpenSolaris的getopt(3)手册页:

http://schillix.sourceforge .net / man / man3c / getopt.3c.html

最后是getopt(1)手册页,以验证过时的$ *:

http:// schillix .sourceforge.net / man / man1 / getopt.1.html

#24 楼

我已经编写了一个bash助手来编写一个不错的bash工具

项目主页:https://gitlab.mbedsys.org/mbedsys/bashopts

示例:

#!/bin/bash -ei

# load the library
. bashopts.sh

# Enable backtrace dusplay on error
trap 'bashopts_exit_handle' ERR

# Initialize the library
bashopts_setup -n "
NAME:
    ./example.sh - This is myapp tool description displayed on help message

USAGE:
    [options and commands] [-- [extra args]]

OPTIONS:
    -h,--help                          Display this help
    -n,--non-interactive true          Non interactive mode - [$bashopts_non_interactive] (type:boolean, default:false)
    -f,--first "John"                  First name - [$first_name] (type:string, default:"")
    -l,--last "Smith"                  Last name - [$last_name] (type:string, default:"")
    --display-name "John Smith"        Display name - [$display_name] (type:string, default:"$first_name $last_name")
    --number 0                         Age - [$age] (type:number, default:0)
    --email                            Email adress - [$email_list] (type:string, default:"")
" -d "This is myapp tool description displayed on help message" -s "$HOME/.config/myapprc" # Declare the options bashopts_declare -n first_name -l first -o f -d "First name" -t string -i -s -r bashopts_declare -n last_name -l last -o l -d "Last name" -t string -i -s -r bashopts_declare -n display_name -l display-name -t string -d "Display name" -e "$first_name $last_name" bashopts_declare -n age -l number -d "Age" -t number bashopts_declare -n email_list -t string -m add -l email -d "Email adress" # Parse arguments bashopts_parse_args "$@" # Process argument bashopts_process_args


将提供帮助:

q4312078q

享受:)

评论


我在Mac OS X上得到了这个:```lib / bashopts.sh:第138行:声明:-A:无效选项声明:用法:声明[-afFirtx] [-p] [name [= value] ...] lib / bashopts.sh中的错误:138。 'declare -x -A bashopts_optprop_name'以状态2退出调用树:1:lib / controller.sh:4 source(...)以状态1退出

–乔什·沃尔夫(Josh Wulf)
17年6月24日在18:07



您需要Bash版本4才能使用它。在Mac上,默认版本为3。您可以使用home brew安装bash 4。

–乔什·沃尔夫(Josh Wulf)
17年6月24日在18:17

#25 楼

这是我的方法-使用正则表达式。


没有getopts
它处理短参数-qwerty的块

它处理短参数-q -w -e

它可以处理长期权--qwerty

您可以将属性传递给短期权或长期权(如果使用的是短期权,则属性将附加到最后一个期权上)
使用空格或=提供属性,但是属性匹配,直到遇到连字符+空格“定界符”为止,因此在--q=qwe ty中,qwe ty是一个属性
它处理以上所有内容的混合,因此-o a -op attr ibute --option=att ribu te --op-tion attribute --option att-ribute是有效的

脚本:

#!/usr/bin/env sh

help_menu() {
  echo "Usage:

  ${0##*/} [-h][-l FILENAME][-d]

Options:

  -h, --help
    display this help and exit

  -l, --logfile=FILENAME
    filename

  -d, --debug
    enable debug
  "
}

parse_options() {
  case $opt in
    h|help)
      help_menu
      exit
     ;;
    l|logfile)
      logfile=${attr}
      ;;
    d|debug)
      debug=true
      ;;
    *)
      echo "Unknown option: ${opt}\nRun ${0##*/} -h for help.">&2
      exit 1
  esac
}
options=$@

until [ "$options" = "" ]; do
  if [[ $options =~ (^ *(--([a-zA-Z0-9-]+)|-([a-zA-Z0-9-]+))(( |=)(([\_\.\?\/\a-zA-Z0-9]?[ -]?[\_\.\?a-zA-Z0-9]+)+))?(.*)|(.+)) ]]; then
    if [[ ${BASH_REMATCH[3]} ]]; then # for --option[=][attribute] or --option[=][attribute]
      opt=${BASH_REMATCH[3]}
      attr=${BASH_REMATCH[7]}
      options=${BASH_REMATCH[9]}
    elif [[ ${BASH_REMATCH[4]} ]]; then # for block options -qwert[=][attribute] or single short option -a[=][attribute]
      pile=${BASH_REMATCH[4]}
      while (( ${#pile} > 1 )); do
        opt=${pile:0:1}
        attr=""
        pile=${pile/${pile:0:1}/}
        parse_options
      done
      opt=$pile
      attr=${BASH_REMATCH[7]}
      options=${BASH_REMATCH[9]}
    else # leftovers that don't match
      opt=${BASH_REMATCH[10]}
      options=""
    fi
    parse_options
  fi
done


评论


像这个。也许只需添加-e param以换行即可。

– mauron85
17年6月21日在6:03

#26 楼

假设我们创建一个名为test_args.sh的shell脚本,如下所示

#!/bin/sh
until [ $# -eq 0 ]
do
  name=${1:1}; shift;
  if [[ -z "" ||  == -* ]] ; then eval "export $name=true"; else eval "export $name="; shift; fi  
done
echo "year=$year month=$month day=$day flag=$flag"


运行以下命令后:

sh test_args.sh  -year 2017 -flag  -month 12 -day 22 


输出为:

year=2017 month=12 day=22 flag=true


评论


这采用与诺亚的回答相同的方法,但安全检查/防护措施较少。这使我们可以在脚本的环境中写入任意参数,并且我很确定您在此处使用eval可能允许命令注入。

–威尔·巴恩威尔
17-10-10在23:57

#27 楼

我想分享我为解析选项所做的事情。
这里的答案未能满足我的某些需求(例如子选项管理,var args,可选args等),因此我不得不提出以下建议:https: //github.com/MihirLuthra/bash_option_parser

假设我们有一个名为fruit的命令,用法如下:

fruit ...
   [-e|—-eat|—-chew]
   [-c|--cut <how> <why>]
   <command> [<args>] 


-e不带args -c需要两个参数,即如何切割和为什么要切割<command>适用于appleorange等子选项(类似于git带有子选项commitpush等)。

所以解析它: br />
source ./option_parser

parse_options \
    ',' 'OPTIONS' 'ARG_CNT' 'ARGS' 'self' '0' ';' '--'  \
                                                        \
    '-e'    , '—-eat' , '—-chew'  '0'                   \
    '-c'    , '—-cut' ,           '1 1'                 \
    'apple'                       'S'                   \
    'orange'                      'S'                   \
                                                        \
    ';' "$@"


现在,如果出现任何使用错误,可以使用option_parser_error_msg进行打印,如下所示:

retval=$?

if [ $retval -ne 0 ]; then
    option_parser_error_msg "$retval" 'OPTIONS'
    exit 1
fi


现在要检查是否通过了某些选项,

if [ -n "${OPTIONS[-c]}" ]
then
    echo "-c was passed"

    # args can be accessed in a 2D-array-like format
    echo "Arg1 to -c = ${ARGS[-c,0]}"
    echo "Arg2 to -c = ${ARGS[-c,1]}"

fi


对于子选项,例如apple,其用法如下:

fruit apple ...
   [—-eat-apple|—-chew-apple]
   [-p|—-peel <how>] 


我们可以检查它是否通过并按以下方式进行解析:

if [ -n "${OPTION[apple]}" ]
then
    shift_count=${OPTION[apple]}

    parse_options \
    ',' 'OPTIONS_apple' 'ARG_CNT_apple' 'ARGS_apple'    \
    'self' "$shift_count" ';' '--'                      \
                                                        \
    '—-eat-apple' , '—-chew-apple'  '0'                 \
    '-p'          , '—-peel'        '1'                 \
                                                        \
    ';' "$@"

fi


子选项解析是通过将$shift_count传递给parse_options来完成的,这使它在将args转移到子选项的args之后开始解析。

自述文件和示例中提供了详细说明。
在存储库中。

#28 楼

使用bash-modules中的模块“参数”

示例:

#!/bin/bash
. import.sh log arguments

NAME="world"

parse_arguments "-n|--name)NAME;S" -- "$@" || {
  error "Cannot parse command line."
  exit 1
}

info "Hello, $NAME!"


#29 楼

混合基于位置和标志的参数

--param = arg(等号分隔)

在位置参数之间自由混合标志:

./script.sh dumbo 127.0.0.1 --environment=production -q -d
./script.sh dumbo --environment=production 127.0.0.1 --quiet -d


可以用相当简洁的方法来完成:

# process flags
pointer=1
while [[ $pointer -le $# ]]; do
   param=${!pointer}
   if [[ $param != "-"* ]]; then ((pointer++)) # not a parameter flag so advance pointer
   else
      case $param in
         # paramter-flags with arguments
         -e=*|--environment=*) environment="${param#*=}";;
                  --another=*) another="${param#*=}";;

         # binary flags
         -q|--quiet) quiet=true;;
                 -d) debug=true;;
      esac

      # splice out pointer frame from positional list
      [[ $pointer -gt 1 ]] \
         && set -- ${@:1:((pointer - 1))} ${@:((pointer + 1)):$#} \
         || set -- ${@:((pointer + 1)):$#};
   fi
done

# positional remain
node_name=
ip_address=


--param arg(以空格分隔)

这很常见更清晰,不要混用--flag=value--flag value样式。

./script.sh dumbo 127.0.0.1 --environment production -q -d


这是一个小读法,但仍然有效

./script.sh dumbo --environment production 127.0.0.1 --quiet -d




# process flags
pointer=1
while [[ $pointer -le $# ]]; do
   if [[ ${!pointer} != "-"* ]]; then ((pointer++)) # not a parameter flag so advance pointer
   else
      param=${!pointer}
      ((pointer_plus = pointer + 1))
      slice_len=1

      case $param in
         # paramter-flags with arguments
         -e|--environment) environment=${!pointer_plus}; ((slice_len++));;
                --another) another=${!pointer_plus}; ((slice_len++));;

         # binary flags
         -q|--quiet) quiet=true;;
                 -d) debug=true;;
      esac

      # splice out pointer frame from positional list
      [[ $pointer -gt 1 ]] \
         && set -- ${@:1:((pointer - 1))} ${@:((pointer + $slice_len)):$#} \
         || set -- ${@:((pointer + $slice_len)):$#};
   fi
done

# positional remain
node_name=
ip_address=


#30 楼

这是一个getopts,它使用最少的代码即可实现解析,并允许您使用带子字符串的eval定义在一种情况下要提取的内容。

基本上eval "local key='val'"

function myrsync() {

        local backup=("${@}") args=(); while [[ $# -gt 0 ]]; do k="";
                case "$k" in
                    ---sourceuser|---sourceurl|---targetuser|---targeturl|---file|---exclude|---include)
                        eval "local ${k:3}=''"; shift; shift    # Past two arguments
                    ;;
                    *)  # Unknown option  
                        args+=(""); shift;                        # Past argument only
                    ;;                                              
                esac                                                
        done; set -- "${backup[@]}"                                 # Restore $@


        echo "${sourceurl}"
}


在这里将变量声明为局部变量而不是全局变量,这是大多数答案。

调用为:

myrsync ---sourceurl http://abc.def.g ---sourceuser myuser ... 


$ {k:3}基本上是一个子字符串,用于从密钥中删除第一个---