如何解析bash中的命令行参数?

How do I parse command line arguments in Bash?

比如,我有一个脚本可以用这行来调用:

1
./myscript -vfd ./foo/bar/someFile -o /fizz/someOtherFile

或者这个:

1
./myscript -v -f -d -o /fizz/someOtherFile ./foo/bar/someFile

在每种情况下(或两种情况的某种组合)$v$f$d都将设置为true$outFile等于/fizz/someOtherFile是什么公认的分析方法?


方法1:使用不带getopt▼显示的bash

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

bash空格分隔(例如,--option argument(不带getopt▼显示)

用法 ./myscript.sh -e conf -s /etc -l /usr/lib /etc/hosts

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

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

case $key in
    -e|--extension)
    EXTENSION="$2"
    shift # past argument
    shift # past value
    ;;
    -s|--searchpath)
    SEARCHPATH="$2"
    shift # past argument
    shift # past value
    ;;
    -l|--lib)
    LIBPATH="$2"
    shift # past argument
    shift # past value
    ;;
    --default)
    DEFAULT=YES
    shift # past argument
    ;;
    *)    # unknown option
    POSITIONAL+=("$1") # 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 $1 ]]; then
    echo"Last line of file specified as non-opt/last argument:"
    tail -1"$1"
fi

bash等于separated(例如,--option=argument(不带getopt▼显示)。

用法./myscript.sh -e=conf -s=/etc -l=/usr/lib /etc/hosts

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

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"Number files in SEARCH PATH with EXTENSION:" $(ls -1"${SEARCHPATH}"/*."${EXTENSION}" | wc -l)
if [[ -n $1 ]]; then
    echo"Last line of file specified as non-opt/last argument:"
    tail -1 $1
fi

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

方法2:使用bash和getopt▼显示

发件人:http://mywiki.wooledge.org/bashfaq/035 getopts

getopt(1)限制(较旧、相对较新的getopt版本):

  • 无法处理空字符串的参数
  • 无法处理嵌入空白的参数

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

另外,posix shell(和其他)提供的getopts没有这些限制。下面是一个简单的getopts示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#!/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: $@"

# End of file

getopts的优点是:

  • 它更便于携带,可以在其他外壳中使用,如dash
  • 它可以以典型的Unix方式自动处理多个单一选项,如-vf filename
  • getopts的缺点是它只能处理短期期权(-h而不是--help而不需要附加代码。

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


    没有答案提到增强型getopt。最热门的答案是误导性的:它忽略了-?vfd样式的短选项(由op请求),位置参数后的选项(也由op请求),并且忽略了解析错误。而是:

    • 从Util Linux或以前的GNU glibc中使用增强的getopt1
    • 它与gnu glibc的c函数getopt_long()一起工作。
    • 具有所有有用的区别特征(其他的没有):
      • 处理参数2中的空格、引用字符甚至二进制
      • 最后可以处理选项:script.sh -o outFile file1 file2 -v
      • 允许=样式的长选项:script.sh --outfile=fileOut --infile fileIn
    • 已经很老了,没有GNU系统缺少这个(例如,任何Linux都有)。
    • 您可以使用:getopt --test→返回值4来测试它的存在性。
    • 其他getopt或外壳内置的getopts的用途有限。

    以下电话

    1
    2
    3
    4
    5
    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

    全部返回

    1
    verbose: y, force: y, debug: y, in: ./foo/bar/someFile, out: /fizz/someOtherFile

    以下为myscript

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    #!/bin/bash
    # saner programming env: these switches turn some bugs into errors
    set -o errexit -o pipefail -o noclobber -o nounset

    ! 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

    # -use ! and PIPESTATUS to get exit code with errexit set
    # -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"$0" --"$@")
    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"$1" in
            -d|--debug)
                d=y
                shift
                ;;
            -f|--force)
                f=y
                shift
                ;;
            -v|--verbose)
                v=y
                shift
                ;;
            -o|--output)
                outFile="$2"
                shift 2
                ;;
            --)
                shift
                break
                ;;
            *)
                echo"Programming error"
                exit 3
                ;;
        esac
    done

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

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

    1enhanced getopt可用于大多数"bash系统",包括cygwin;on os x try brew install gnu getopt or sudo port install getopt2the posix exec()conventions have no reliable way to pass binary null in command line arguments;those bytes prematically end the arguments31997年或之前发布的第一个版本(我只跟踪到1997年)


    发件人:digitalpeer.com,稍作修改

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    #!/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/[^=]*=//'`


    getopt()/getopts()是一个很好的选择。从这里被盗:

    The simple use of"getopt" is shown in this mini-script:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    #!/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

    What we have said is that any of -a,
    -b, -c or -d will be allowed, but that -c is followed by an argument (the"c:" says that).

    If we call this"g" and try it out:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    bash-2.05a$ ./g -abc foo
    Before getopt
    -abc
    foo
    After getopt
    -->-a
    -->-b
    -->-c
    -->foo
    -->--

    We start with two arguments, and
    "getopt" breaks apart the options and
    puts each in its own argument. It also
    added"--".


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

    • 处理-n arg--name=arg
    • 允许在结尾使用参数
    • 如果有拼写错误,则显示正常错误
    • 兼容,不使用bashims
    • 可读,不需要在循环中保持状态

    希望对某人有用。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    while ["$#" -gt 0 ]; do
      case"$1" in
        -n) name="$2"; shift 2;;
        -p) pidfile="$2"; shift 2;;
        -l) logfile="$2"; shift 2;;

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

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


    更简洁的方式

    脚本

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

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

    echo"Should deploy? $deploy"
    echo"Should uglify? $uglify"

    用途:

    1
    2
    3
    4
    5
    ./script.sh -d dev -u

    # OR:

    ./script.sh --deploy dev --uglify


    这个问题我晚了4年,但我想还钱。我把前面的答案作为一个起点来整理我以前的特殊参数解析。然后我重构出以下模板代码。它使用=或空格分隔的参数处理长参数和短参数,以及组合在一起的多个短参数。最后,它将任何非参数参数重新插入$1,$2..变量。希望它有用。

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

    # NOTICE: Uncomment if your script depends on bashisms.
    #if [ -z"$BASH_VERSION" ]; then bash $0 $@ ; 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"$1" ]; do
            # Copy so we can modify it (can't modify $1)
            OPT="$1"
            # 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="$2"
                                    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 ($1 $2 ..)
    eval set -- $REMAINS


    echo -e"After:
     configfile='$CONFIGFILE'
     force='$FORCE'
     retry='$RETRY'
     remains='$REMAINS'"

    for i ; do echo - $i ; done


    我的答案很大程度上是基于BrunoBronosky的答案,但是我把他的两个纯bash实现混搭成了一个我经常使用的实现。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    # As long as there is at least one more argument, keep looping
    while [[ $# -gt 0 ]]; do
        key="$1"
        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="$1"
            ;;
            # 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

    这允许您同时具有空格分隔的选项/值以及相等的定义值。

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

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

    以及:

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

    两者的最终结果应该相同。

    赞成的意见:

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

    • 使用任何可以在bash中使用的arg名称

      • 意思是-a或-a r g或-a r g或-a-r-g或其他
    • 纯粹的狂欢。无需学习/使用getopt或getopts

    欺骗:

    • 无法组合参数

      • 意思是不-ABC。你必须做-A-B-C

    这些是我能想到的唯一的利弊


    我发现在脚本中编写可移植解析的问题非常令人沮丧,以至于我编写了argbash——一个可以为脚本生成参数解析代码的FOSS代码生成器,它还有一些好的特性:

    https://argbash.io网站


    我觉得这个很简单,可以使用:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    #!/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

    调用示例:

    1
    2
    3
    4
    5
    6
    ./myscript -v -do /fizz/someOtherFile -f ./foo/bar/someFile
    OPT:v
    OPT:d
    OPT:o OPTARG:/fizz/someOtherFile
    OPT:f
    ARG:./foo/bar/someFile


    扩展@guneysus的优秀答案,这里有一个调整,允许用户使用他们喜欢的任何语法,例如

    1
    command -x=myfilename.ext --another_switch

    VS

    1
    command -x myfilename.ext --another_switch

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

    这种"模糊解释"可能不符合您的喜好,但是如果您正在制作可与其他实用程序互换的脚本(就像我的脚本一样,它必须与ffmpeg一起工作),那么灵活性是有用的。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    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

    如果您安装了getopts,并且打算在同一平台上运行它,那么getopts工作得很好。在这方面,OSX和Linux(例如)表现不同。

    这里有一个(非getopts)解决方案,它支持equals、non equals和boolean标志。例如,您可以这样运行脚本:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    ./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

    我给您提供了函数parse_params,它将解析命令行中的参数。

  • 它是一个纯粹的bash解决方案,没有额外的实用程序。
  • 不会污染全球范围。
  • 轻松地返回简单易用的变量,您可以在此基础上构建进一步的逻辑。
  • 参数前破折号的数量无关紧要(--all等于-all等于all=all)
  • 下面的脚本是一个复制粘贴工作演示。参见show_use函数了解如何使用parse_params函数。

    局限性:

  • 不支持空格分隔的参数(-d 1)
  • 参数名称将丢失破折号,因此--any-param-anyparam是等效的
  • 必须在bash函数内部使用eval $(parse_params"$@")(它不在全局范围内工作)
  • 1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    #!/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 [["$1" !="" ]]; do
            # Escape asterisk to prevent bash asterisk expansion, and quotes to prevent string breakage
            _escaped=${1/\*/\'"*"\'}
            _escaped=${_escaped//\'/\\'}
            _escaped=${_escaped//"/\\\
    <div class="
    suo-content">[collapse title=""]<ul><li>要使用演示来解析bash脚本中的参数,只需执行<wyn>show_use"$@"</wyn>。</li><li>基本上,我发现github.com/renatosilva/easyoptions以相同的方式执行相同的操作,但比这个函数要大一些。</li></ul>[/collapse]</div><hr>
    <p>
    EasyOptions does not require any parsing:
    </p>

    [cc lang="
    bash"]## 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


    这是我在函数中的操作方式,以避免在堆栈的较高位置同时中断getopts的运行:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    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
    ...
    }

    请注意,EDOCX1[0]是AT&T的一个短暂的错误。

    Getopt创建于1984年,但由于它实际上不可用,已于1986年埋葬。

    证明getopt非常过时的一个事实是,getopt(1)手册页仍然提到"$*",而不是"$@",1986年与getopts(1)shell一起添加到bourne shell中,以处理内部空间的争论。

    顺便说一句:如果您对分析shell脚本中的长选项感兴趣,那么您可能会感兴趣的是,libc(solaris)和ksh93中的getopt(3)实现都添加了一个统一的长选项实现,它支持长选项作为短选项的别名。这使得ksh93Bourne Shell通过getopts为长选项实现统一的接口。

    Bourne Shell手册页上的长选项示例:

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

    显示BourneShell和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网站


    我想提供我的选项解析版本,它允许以下内容:

    1
    2
    3
    4
    5
    6
    -s p1
    --stage p1
    -w somefolder
    --workfolder somefolder
    -sw p1 somefolder
    -e=hello

    还允许这样做(可能是不需要的):

    1
    2
    3
    -s--workfolder p1 somefolder
    -se=hello p1
    -swe=hello p1 somefolder

    在使用之前,您必须决定是否在选项上使用if=这是为了保持代码的整洁(ish)。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    while [[ $# > 0 ]]
    do
        key="$1"
        while [[ ${key+x} ]]
        do
            case $key in
                -s*|--stage)
                    STAGE="$2"
                    shift # option has parameter
                    ;;
                -w*|--workfolder)
                    workfolder="$2"
                    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


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

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

    运行以下命令后:

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

    输出将是:

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


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

    这是我的解决方案。它非常灵活,不像其他软件包,不应该需要外部包,并且干净地处理剩余的参数。

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

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

    1
    2
    3
    ./myscript -flag flagvariable -otherflag flagvar2
    echo $flag $otherflag
    flagvariable flagvar2

    主代码(简短的版本、详细的示例以及出错的版本):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    #!/usr/bin/env bash
    #shebang.io
    validflags="rate time number"
    count=1
    for arg in $@
    do
        match=0
        argval=$1
        for flag in $validflags
        do
            sflag="-"$flag
            if ["$argval" =="$sflag" ]
            then
                declare $flag=$2
                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

    包含内置Echo演示的详细版本:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    #!/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=$1
    #   argval=$(echo $@ | cut -d ' ' -f$count)
        for flag in $validflags
        do
                sflag="-"$flag
            if ["$argval" =="$sflag" ]
            then
                declare $flag=$2
                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: $1 arg2: $2

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

    最后一个,如果传递了一个无效参数,这个参数就会出错。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    #!/usr/bin/env bash
    #shebang.io
    rate=30
    time=30
    number=30
    validflags="rate time number"
    count=1
    for arg in $@
    do
        argval=$1
        match=0
            if ["${argval:0:1}" =="-" ]
        then
            for flag in $validflags
            do
                    sflag="-"$flag
                if ["$argval" =="$sflag" ]
                then
                    declare $flag=$2
                    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

    优点:它的功能,操控性很好。它保留了许多其他解决方案所没有的未使用的参数。它还允许在脚本中不手工定义的情况下调用变量。如果没有给出相应的参数,它还允许预先填充变量。(请参阅详细示例)。

    缺点:无法解析单个复杂的参数字符串,例如-xcvf将作为单个参数处理。不过,您可以很容易地在我的代码中编写额外的代码来添加这个功能。


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

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

    例子:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    #!/bin/bash -ei

    # load the library
    . bashopts.sh

    # Enable backtrace dusplay on error
    trap 'bashopts_exit_handle' ERR

    # Initialize the library
    bashopts_setup -n"$0" -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

    将给予帮助:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    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:"")

    享受:


    混合基于位置和标志的参数--参数=参数(等于分隔符)

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

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

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    # 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=$1
    ip_address=$2

    --参数参数(空格分隔)

    通常情况下,不混合--flag=value--flag value样式会更清楚。

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

    这有点冒险,但仍然有效

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

    来源

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    # 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=$1
    ip_address=$2

    这是我的方法-使用regexp。

    • 没有胜利者
    • 它处理短参数-qwerty块。
    • 它处理短参数-q -w -e
    • 它处理长期权--qwerty
    • 可以将属性传递给短选项或长选项(如果使用短选项块,则属性将附加到最后一个选项)
    • 您可以使用空格或=提供属性,但属性匹配直到遇到连字符+空格"分隔符",因此在--q=qwe tyqwe ty中是一个属性。
    • 它处理以上所有的混合,所以-o a -op attr ibute --option=att ribu te --op-tion attribute --option att-ribute是有效的

    脚本:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    #!/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}
    Run ${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


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

    1
    2
    source argparser.sh
    parse_args"$@"

    就这么简单。将使用与参数同名的变量填充环境。


    使用bash模块中的模块"参数"

    例子:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    #!/bin/bash
    . import.sh log arguments

    NAME="world"

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

    info"Hello, $NAME!"


    这也可能有助于了解,您可以设置一个值,如果有人提供输入,则使用该值覆盖默认值。

    myscript.sh-f./serverlist.txt或仅/myscript.sh(采用默认值)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
        #!/bin/bash
        # --- set the value, if there is inputs, override the defaults.

        HOME_FOLDER="${HOME}/owned_id_checker"
        SERVER_FILE_LIST="${HOME_FOLDER}/server_list.txt"

        while [[ $# > 1 ]]
        do
        key="$1"
        shift

        case $key in
            -i|--inputlist)
            SERVER_FILE_LIST="$1"
            shift
            ;;
        esac
        done


        echo"SERVER LIST   = ${SERVER_FILE_LIST}"

    另一个没有getopt▼显示、posix和旧的unix风格的解决方案

    与布鲁诺·布朗斯基发布的解决方案类似,这里没有使用getopt(s)

    我的解决方案的主要区别在于,它允许选项串联在一起,就像tar -xzf foo.tar.gz等于tar -x -z -f foo.tar.gz。就像在tarps等中一样,前导连字符对于短选项块是可选的(但这很容易改变)。也支持长选项(但当块以一个开头时,需要两个前导连字符)。

    带示例选项的代码

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

    echo
    echo"POSIX-compliant getopt(s)-free old-style-supporting option parser from phk@[se.unix]"
    echo

    print_usage() {
      echo"Usage:

      $0 {a|b|c} [ARG...]

    Options:

      --aaa-0-args
      -a
        Option without arguments.

      --bbb-1-args ARG
      -b ARG
        Option with one argument.

      --ccc-2-args ARG1 ARG2
      -c ARG1 ARG2
        Option with two arguments.

    "
    >&2
    }

    if [ $# -le 0 ]; then
      print_usage
      exit 1
    fi

    opt=
    while :; do

      if [ $# -le 0 ]; then

        # no parameters remaining -> end option parsing
        break

      elif [ !"$opt" ]; then

        # we are at the beginning of a fresh block
        # remove optional leading hyphen and strip trailing whitespaces
        opt=$(echo"$1" | sed 's/^-\?\([a-zA-Z0-9\?-]*\)/\1/')

      fi

      # get the first character -> check whether long option
      first_chr=$(echo"$opt" | awk '{print substr($1, 1, 1)}')
      ["$first_chr" = - ] && long_option=T || long_option=F

      # note to write the options here with a leading hyphen less
      # also do not forget to end short options with a star
      case $opt in

        -)

          # end of options
          shift
          break
          ;;

        a*|-aaa-0-args)

          echo"Option AAA activated!"
          ;;

        b*|-bbb-1-args)

          if ["$2" ]; then
            echo"Option BBB with argument '$2' activated!"
            shift
          else
            echo"BBB parameters incomplete!">&2
            print_usage
            exit 1
          fi
          ;;

        c*|-ccc-2-args)

          if ["$2" ] && ["$3" ]; then
            echo"Option CCC with arguments '$2' and '$3' activated!"
            shift 2
          else
            echo"CCC parameters incomplete!">&2
            print_usage
            exit 1
          fi
          ;;

        h*|\?*|-help)

          print_usage
          exit 0
          ;;

        *)

          if ["$long_option" = T ]; then
            opt=$(echo"$opt" | awk '{print substr($1, 2)}')
          else
            opt=$first_chr
          fi
          printf 'Error: Unknown option:"%s"
    '
    "$opt">&2
          print_usage
          exit 1
          ;;

      esac

      if ["$long_option" = T ]; then

        # if we had a long option then we are going to get a new block next
        shift
        opt=

      else

        # if we had a short option then just move to the next character
        opt=$(echo"$opt" | awk '{print substr($1, 2)}')

        # if block is now empty then shift to the next one
        ["$opt" ] || shift

      fi

    done

    echo"Doing something..."

    exit 0

    有关示例用法,请参阅下面的示例。

    带参数的选项位置

    就其价值而言,带有参数的选项不是最后一个(只需要长选项)。因此,尽管例如在EDOCX1(至少在某些实现中)中,f选项必须是最后一个,因为文件名如下(tar xzf bar.tar.gz有效,但tar xfz bar.tar.gz无效),但这里不是这样(见后面的示例)。

    带参数的多个选项

    作为另一个奖励,选项参数按选项的顺序由具有所需选项的参数使用。只需查看命令行abc X Y Z-abc X Y Z的脚本输出:

    1
    2
    3
    Option AAA activated!
    Option BBB with argument 'X' activated!
    Option CCC with arguments 'Y' and 'Z' activated!

    同时连接的长选项

    此外,在选项块中也可以有长选项,因为它们最后出现在块中。因此,以下命令行都是等效的(包括处理选项及其参数的顺序):

    • -cba Z Y X
    • cba Z Y X
    • -cb-aaa-0-args Z Y X
    • -c-bbb-1-args Z Y X -a
    • --ccc-2-args Z Y -ba X
    • c Z Y b X a
    • -c Z Y -b X -a
    • --ccc-2-args Z Y --bbb-1-args X --aaa-0-args

    所有这些都会导致:

    1
    2
    3
    4
    Option CCC with arguments 'Z' and 'Y' activated!
    Option BBB with argument 'X' activated!
    Option AAA activated!
    Doing something...

    不在这个解决方案中可选参数

    带有可选参数的选项应该可以通过一点工作来实现,例如,通过向前看是否有一个没有连字符的块;然后,用户需要在每个块前面放置一个连字符,然后跟随一个带有可选参数的参数的块。也许这太复杂了,无法与用户通信,所以在这种情况下,最好只需要一个前导连字符。

    有了多个可能的参数,事情变得更加复杂。我建议不要通过判断一个参数是否适合它来做出明智的选择(例如,有了一个选项,只需要一个数字作为可选参数),因为这在将来可能会被打破。

    我个人更喜欢附加的选项而不是可选的论点。

    用等号引入的选项参数

    就像可选参数一样,我不喜欢这个(顺便说一句,是否有一个线程可以讨论不同参数样式的优缺点?)但是如果你想这样做,你可以自己实现它,就像在http://mywiki.wooledge.org/bashfaq/035 manual循环中使用--long-with-arg=?*case语句,然后去掉等号(这是btw网站说,通过某种努力可以实现参数连接,但"留给读者作为练习",whic他让我听从他们的话,但我从零开始。

    其他音符

    与posix兼容,即使是在我必须处理的古代busybox设置上(例如,cutheadgetopts丢失)。


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

    • --longopt=val扩展为--longopt val
    • -xyz扩展为-x -y -z
    • 支持--表示标志结束
    • 显示意外选项的错误
    • 紧凑易读的选项开关
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    #!/bin/bash

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

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

    invalid() {
      echo"ERROR: Unrecognized argument: $1">&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="$1"; 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}${1}" in
        -h|--help)      usage 0 ;;
        -p|--password)  shift; PASSWORD="$1" ;;
        -u|--username)  shift; USERNAME="$1" ;;
        -n|--name)      shift; names+=("$1") ;;
        -q|--quiet)     QUIET=1 ;;
        -C|--copy)      COPY=1 ;;
        -N|--notify)    NOTIFY=1 ;;
        --stdin)        READ_STDIN=1 ;;
        --)             END_OF_OPT=1 ;;
        -*)             invalid"$1" ;;
        *)              POSITIONAL+=("$1") ;;
      esac
      shift
    done

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

    当我试着回答这个问题的时候,它的首要答案似乎有点笨拙——这是我的解决方案,我发现它更强大:

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

    while [[ $# -gt 0 ]]
    do
    key="$1"
    case $key in
        -b|--boolean-arg)
        boolean_arg=true
        shift
        ;;
        -a|--arg-with-value)
        arg_with_value="$2"
        shift
        shift
        ;;
        -*)
        echo"Unknown option: $1"
        exit 1
        ;;
        *)
        arg_num=$(( $arg_num + 1 ))
        case $arg_num in
            1)
            first_normal_arg="$1"
            shift
            ;;
            2)
            second_normal_arg="$1"
            shift
            ;;
            *)
            bad_args=TRUE
        esac
        ;;
    esac
    done

    # Handy to have this here when adding arguments to
    # see if they're working. Just edit the '0' to be '1'.
    if [[ 0 == 1 ]]; then
        echo"first_normal_arg: $first_normal_arg"
        echo"second_normal_arg: $second_normal_arg"
        echo"boolean_arg: $boolean_arg"
        echo"arg_with_value: $arg_with_value"
        exit 0
    fi

    if [[ $bad_args == TRUE || $arg_num < 2 ]]; then
        echo"Usage: $(basename"$0") <first-normal-arg> <second-normal-arg> [--boolean-arg] [--arg-with-value VALUE]"
        exit 1
    fi

    这个例子演示了如何使用getopteval以及HEREDOCshift来处理短参数和长参数,这些参数都有或没有以下所需的值。此外,switch/case语句简洁易懂。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    #!/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, don't change any files

    HEREDOC

    }  

    # initialize variables
    progname=$(basename $0)
    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:"$1" \$2:"$2""
      case"$1" in
        -h | --help ) usage; exit; ;;
        -n | --num ) num_str="$2"; shift 2 ;;
        -t | --time ) time_str="$2"; 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

    上面脚本中最重要的几行是:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    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"$1" in
        -h | --help ) usage; exit; ;;
        -n | --num ) num_str="$2"; shift 2 ;;
        -t | --time ) time_str="$2"; shift 2 ;;
        --dry-run ) dryrun=1; shift ;;
        -v | --verbose ) verbose=$((verbose + 1)); shift ;;
        -- ) shift; break ;;
        * ) break ;;
      esac
    done

    简明扼要,可读性强,处理几乎所有事情(imho)。

    希望能帮助别人。


    这是我改进的布鲁诺·布朗斯基用变量数组的答案的解决方案。

    它允许您混合参数位置,并为您提供一个参数数组,保留没有选项的顺序。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    #!/bin/bash

    echo $@

    PARAMS=()
    SOFT=0
    SKIP=()
    for i in"$@"
    do
    case $i in
        -n=*|--skip=*)
        SKIP+=("${i#*=}")
        ;;
        -s|--soft)
        SOFT=1
        ;;
        *)
            # unknown option
            PARAMS+=("$i")
        ;;
    esac
    done
    echo"SKIP            = ${SKIP[@]}"
    echo"SOFT            = $SOFT"
        echo"Parameters:"
        echo ${PARAMS[@]}

    将输出,例如:

    1
    2
    3
    4
    5
    6
    $ ./test.sh parameter -s somefile --skip=.c --skip=.obj
    parameter -s somefile --skip=.c --skip=.obj
    SKIP            = .c .obj
    SOFT            = 1
    Parameters:
    parameter somefile


    简单且易于修改,参数可以按任意顺序排列。这可以修改为采用任何形式的参数(-a、-a、a等)。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    for arg in"$@"
    do
       key=$(echo $arg | cut -f1 -d=)`
       value=$(echo $arg | cut -f2 -d=)`
       case"$key" in
            name|-name)      read_name=$value;;
            id|-id)          read_id=$value;;
            *)               echo"I dont know what to do with this"
       ease
    done