关于bash:是否可以将getopts与位置参数混合?

Is mixing getopts with positional parameters possible?

我想设计一个shell脚本作为几个脚本的包装。 我想使用getoptsmyshell.sh指定参数,并将其余参数以相同的顺序传递给指定的脚本。

如果myshell.sh执行如下:

1
2
3
4
5
myshell.sh -h hostname -s test.sh -d waittime param1 param2 param3

myshell.sh param1 param2 -h hostname param3 -d waittime -s test.sh

myshell.sh param1 -h hostname -d waittime -s test.sh param2 param3

以上所有内容都应该可以称为

1
test.sh param1 param2 param3

是否可以利用myshell.sh中的options参数并将其余参数发布到基础脚本中?


抱歉在旧主题上发表评论,但以为我会为那些正在寻找如何执行此操作的人发布信息...

我想做与OP类似的事情,并且在这里和这里都找到了所需的相关信息。

本质上,如果您想执行以下操作:

1
script.sh [options] ARG1 ARG2

然后像这样获得您的选择:

1
2
3
4
5
6
7
8
while getopts"h:u:p:d:" flag; do
case"$flag" in
    h) HOSTNAME=$OPTARG;;
    u) USERNAME=$OPTARG;;
    p) PASSWORD=$OPTARG;;
    d) DATABASE=$OPTARG;;
esac
done

然后,您可以像下面这样获取位置参数:

1
2
ARG1=${@:$OPTIND:1}
ARG2=${@:$OPTIND+1:1}

通过上面的链接可以获得更多信息和详细信息。

希望有帮助!!


混合opts和args:

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
ARGS=""
echo"options :"
while [ $# -gt 0 ]
do
    unset OPTIND
    unset OPTARG
    while getopts as:c:  options
    do
    case $options in
            a)  echo"option a  no optarg"
                    ;;
            s)  serveur="$OPTARG"
                    echo"option s = $serveur"
                    ;;
            c)  cible="$OPTARG"
                    echo"option c = $cible"
                    ;;
        esac
   done
   shift $((OPTIND-1))
   ARGS="${ARGS} $1"
   shift
done

echo"ARGS : $ARGS"
exit 1

结果:

1
2
3
4
5
6
bash test.sh  -a  arg1 arg2 -s serveur -c cible  arg3
options :
option a  no optarg
option s = serveur
option c = cible
ARGS :  arg1  arg2  arg3


getopts不会解析param1-n选项的混合。

最好将param1-3放入其他选项中。

此外,您可以使用现有的库,例如shflags。它非常聪明,易于使用。

最后一种方法是编写自己的函数来解析参数,而无需getopts,只需通过case构造迭代所有参数即可。这是最困难的方法,但却是完全符合您的期望的唯一方法。


只需混搭一个快捷方式,即可轻松处理选项和位置参数的混合体(在$ @中仅保留位置参数):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#!/bin/bash
while [ ${#} -gt 0 ];do OPTERR=0;OPTIND=1;getopts"p:o:hvu" arg;case"$arg" in
        p) echo"Path:   [$OPTARG]" ;;
        o) echo"Output: [$OPTARG]" ;;
        h) echo"Help"              ;;
        v) echo"Version"           ;;
    \?) SET+=("$1")                                           ;;
    *) echo"Coding error: '-$arg' is not handled by case">&2 ;;
esac;shift;["" !="$OPTARG" ] && shift;done
[ ${#SET[@]} -gt 0 ] && set"""${SET[@]}" && shift

echo -e"=========
Leftover (positional) parameters (count=$#) are:"

for i in `seq $#`;do echo -e"\t$i> [${!i}]";done

样本输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
[root@hots:~]$ ./test.sh 'aa bb' -h -v -u -q 'cc dd' -p 'ee ff' 'gg hh' -o ooo
Help
Version
Coding error: '-u' is not handled by case
Path:   [ee ff]
Output: [ooo]
=========
Leftover (positional) parameters (count=4) are:
        1> [aa bb]
        2> [-q]
        3> [cc dd]
        4> [gg hh]
[root@hots:~]$


我想了一种方法,可以将getopts扩展为真正混合选项和位置参数。这个想法是在调用getopts和将找到的任何位置参数分配给n1n2n3等之间交替进行:

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
parse_args() {
    _parse_args 1"$@"
}

_parse_args() {
    local n="$1"
    shift

    local options_func="$1"
    shift

    local OPTIND
   "$options_func""$@"
    shift $(( OPTIND - 1 ))

    if [ $# -gt 0 ]; then
        eval test -n \${n$n+x}
        if [ $? -eq 0 ]; then
            eval n$n="\$1"
        fi

        shift
        _parse_args $(( n + 1 ))"$options_func""$@"
    fi
}

然后在OP的情况下,您可以像这样使用它:

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
main() {
    local n1='' n2='' n3=''
    local duration hostname script

    parse_args parse_main_options"$@"

    echo"n1 = $n1"
    echo"n2 = $n2"
    echo"n3 = $n3"
    echo"duration = $duration"
    echo"hostname = $hostname"
    echo"script   = $script"
}

parse_main_options() {
    while getopts d:h:s: opt; do
        case"$opt" in
            d) duration="$OPTARG" ;;
            h) hostname="$OPTARG" ;;
            s) script="$OPTARG"   ;;
        esac
    done
}

main"$@"

运行它会显示输出:

1
2
3
4
5
6
7
$ myshell.sh param1 param2 -h hostname param3 -d waittime -s test.sh
n1 = param1
n2 = param2
n3 = param3
duration = waittime
hostname = hostname
script   = test.sh

只是概念的证明,但对某人可能有用。

注意:如果使用parse_args的一个函数调用使用parse_args的另一个函数并且外部函数声明例如local n4='',但内部的不包含,并且将4个或更多位置参数传递给内部函数


您可以直接实现自己的bash参数解析器,而不必使用getopts。以此为工作示例。它可以同时处理名称和位置参数。

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

function parse_command_line() {
    local named_options;
    local parsed_positional_arguments;

    yes_to_all_questions="";
    parsed_positional_arguments=0;

    named_options=(
           "-y""--yes"
           "-n""--no"
           "-h""--help"
           "-s""--skip"
           "-v""--version"
        );

    function validateduplicateoptions() {
        local item;
        local variabletoset;
        local namedargument;
        local argumentvalue;

        variabletoset="${1}";
        namedargument="${2}";
        argumentvalue="${3}";

        if [[ -z"${namedargument}" ]]; then
            printf"Error: Missing command line option for named argument '%s', got '%s'...\
"
"${variabletoset}""${argumentvalue}";
            exit 1;
        fi;

        for item in"${named_options[@]}";
        do
            if [["${item}" =="${argumentvalue}" ]]; then
                printf"Warning: Named argument '%s' got possible invalid option '%s'...\
"
"${namedargument}""${argumentvalue}";
                exit 1;
            fi;
        done;

        if [[ -n"${!variabletoset}" ]]; then
            printf"Warning: Overriding the named argument '%s=%s' with '%s'...\
"
"${namedargument}""${!variabletoset}""${argumentvalue}";
        else
            printf"Setting '%s' named argument '%s=%s'...\
"
"${thing_name}""${namedargument}""${argumentvalue}";
        fi;
        eval"${variabletoset}='${argumentvalue}'";
    }

    # https://stackoverflow.com/questions/2210349/test-whether-string-is-a-valid-integer
    function validateintegeroption() {
        local namedargument;
        local argumentvalue;

        namedargument="${1}";
        argumentvalue="${2}";

        if [[ -z"${2}" ]];
        then
            argumentvalue="${1}";
        fi;

        if [[ -n"$(printf"%s""${argumentvalue}" | sed s/[0-9]//g)" ]];
        then
            if [[ -z"${2}" ]];
            then
                printf"Error: The %s positional argument requires a integer, but it got '%s'...\
"
"${parsed_positional_arguments}""${argumentvalue}";
            else
                printf"Error: The named argument '%s' requires a integer, but it got '%s'...\
"
"${namedargument}""${argumentvalue}";
            fi;
            exit 1;
        fi;
    }

    function validateposisionaloption() {
        local variabletoset;
        local argumentvalue;

        variabletoset="${1}";
        argumentvalue="${2}";

        if [[ -n"${!variabletoset}" ]]; then
            printf"Warning: Overriding the %s positional argument '%s=%s' with '%s'...\
"
"${parsed_positional_arguments}""${variabletoset}""${!variabletoset}""${argumentvalue}";
        else
            printf"Setting the %s positional argument '%s=%s'...\
"
"${parsed_positional_arguments}""${variabletoset}""${argumentvalue}";
        fi;
        eval"${variabletoset}='${argumentvalue}'";
    }

    while [["${#}" -gt 0 ]];
    do
        case ${1} in
            -y|--yes)
                yes_to_all_questions="${1}";
                printf"Named argument '%s' for yes to all questions was triggered.\
"
"${1}";
                ;;

            -n|--no)
                yes_to_all_questions="${1}";
                printf"Named argument '%s' for no to all questions was triggered.\
"
"${1}";
                ;;

            -h|--help)
                printf"Print help here\
"
;
                exit 0;
                ;;

            -s|--skip)
                validateintegeroption"${1}""${2}";
                validateduplicateoptions g_installation_model_skip_commands"${1}""${2}";
                shift;
                ;;

            -v|--version)
                validateduplicateoptions branch_or_tag"${1}""${2}";
                shift;
                ;;

            *)
                parsed_positional_arguments=$((parsed_positional_arguments+1));

                case ${parsed_positional_arguments} in
                    1)
                        validateposisionaloption branch_or_tag"${1}";
                        ;;

                    2)
                        validateintegeroption"${1}";
                        validateposisionaloption g_installation_model_skip_commands"${1}";
                        ;;

                    *)
                        printf"ERROR: Extra positional command line argument '%s' found.\
"
"${1}";
                        exit 1;
                        ;;
                esac;
                ;;
        esac;
        shift;
    done;

    if [[ -z"${g_installation_model_skip_commands}" ]];
    then
        g_installation_model_skip_commands="0";
    fi;
}

您可以将此函数称为:

1
2
3
#!/bin/bash
source ./function_file.sh;
parse_command_line"${@}";

用法示例:

1
2
3
4
./test.sh as 22 -s 3
Setting the 1 positional argument 'branch_or_tag=as'...
Setting the 2 positional argument 'skip_commands=22'...
Warning: Overriding the named argument '-s=22' with '3'...

参考文献:

  • example_installation_model.sh.md
  • 检查正确数量的参数
  • https://unix.stackexchange.com/questions/129391/passing-named-arguments-to-shell-scripts
  • 如何在bash中使用getopts的示例

  • 您可以尝试以下技巧:使用optargs进行while循环后,只需使用此代码段

    1
    2
    3
    4
    5
    6
    7
    8
    #shift away all the options so that only positional agruments
    #remain in $@

    for (( i=0; i<OPTIND-1; i++)); do
        shift
    done

    POSITIONAL="$@"

    但是,这种方法有一个错误:

    第一个位置参数之后的所有选项均由getopts合并,并被视为位置参数-如果事件正确,则这些选项(请参见示例输出:-m和-c属于位置参数)

    也许还有更多的错误...

    看整个例子:

    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
    while getopts :abc opt; do
        case $opt in
            a)
            echo found: -a
            ;;
            b)
            echo found: -b
            ;;
            c)
            echo found: -c
            ;;
            \?) echo found bad option: -$OPTARG
            ;;
        esac
    done

    #OPTIND-1 now points to the first arguments not beginning with -

    #shift away all the options so that only positional agruments
    #remain in $@

    for (( i=0; i<OPTIND-1; i++)); do
        shift
    done

    POSITIONAL="$@"

    echo"positional: $POSITIONAL"

    输出:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    [root@host ~]# ./abc.sh -abc -de -fgh -bca haha blabla -m -c
    found: -a
    found: -b
    found: -c
    found bad option: -d
    found bad option: -e
    found bad option: -f
    found bad option: -g
    found bad option: -h
    found: -b
    found: -c
    found: -a
    positional: haha blabla -m -c

    Unix选项处理有一些标准,在Shell编程中,getopts是实施它们的最佳方法。几乎所有现代语言(perl,python)在getopts上都有一个变体。

    这只是一个简单的例子:

    1
    command [ options ] [--] [ words ]
  • 每个选项必须以破折号-开头,并且必须由单个字符组成。

  • GNU项目引入了Long Options,从两个破折号--开始,
    后跟一个完整的单词--long_option。 AST KSH项目的getopts还支持长选项,并且长选项以单破折号-开头,如find(1)所示。

  • 选项可能会也可能不会出现参数。

  • 任何不以破折号-开头的单词将结束选项处理。

  • 字符串--必须被跳过,并将结束选项处理。

  • 剩下的所有参数都保留为位置参数。

  • 开放小组在Utility Argument Syntax上有一节

    埃里克·雷蒙德(Eric Raymond)的Unix编程艺术(Art of Unix Programming)中有一章介绍了传统的unix选项字母及其含义。