bash 是否可以将 getopts 与位置参数混合使用?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/11742996/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-09 22:30:03  来源:igfitidea点击:

Is mixing getopts with positional parameters possible?

bashshellparameters

提问by Bharat Sinha

I want to design a shell script as a wrapper for a couple of scripts. I would like to specify parameters for myshell.shusing getoptsand pass the remaining parameters in the same order to the script specified.

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

If myshell.shis executed like:

如果myshell.sh执行如下:

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

All of the above should be able to call as

以上所有应该都可以调用为

test.sh param1 param2 param3

Is it possible to utilize the options parameters in themyshell.shand post remaining parameters to underlying script?

是否可以利用中的选项参数myshell.sh并将剩余参数发布到底层脚本?

回答by DRendar

Sorry for commenting on an old thread, but thought I'd post for those, like me who were searching on how to do this...

很抱歉对旧线程发表评论,但我想我会为那些像我这样正在寻找如何做到这一点的人发帖...

I wanted to do something similar to the OP, and I found the relevant information I required hereand here

我想做一些类似于OP的事情,我在这里这里找到了我需要的相关信息

Essentially if you want to do something like:

基本上,如果你想做这样的事情:

script.sh [options] ARG1 ARG2

Then get your options like this:

然后让你的选择是这样的:

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

And then you can get your positional arguments like this:

然后你可以得到这样的位置参数:

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

More information and details are available through the link above.

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

Hope that helps!!

希望有帮助!!

回答by chichi

Mix opts and args :

混合 opts 和 args :

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}  "
   shift
done

echo "ARGS : $ARGS"
exit 1

Result:

结果:

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

回答by rush

getoptswon't parse the mix of param1and -noptions.

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

It is much better to put param1-3 into options like others.

像其他人一样将 param1-3 放入选项中要好得多。

Furthermore you can use already existing libraries such as shflags. It is pretty smart and it is easy to use.

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

And the last way is to write your own function to parse params without getopts, just iterating all params through caseconstruction. It is the hardest way but it is the only way to match your expectations exactly.

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

回答by Michael Kropat

I thought up one way that getoptscan be extended to truly mix options and positional parameters. The idea is to alternate between calling getoptsand assigning any positional parameters found to n1, n2, n3, etc.:

我想出了一种getopts可以扩展到真正混合选项和位置参数的方法。这样做是为了交替调用之间getopts和分配任何位置参数发现n1n2n3等:

parse_args() {
    _parse_args 1 "$@"
}

_parse_args() {
    local n=""
    shift

    local options_func=""
    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
}

Then in the OP's case, you could use it like:

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

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 "$@"

Running it shows the output:

运行它显示输出:

$ 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

Just a proof of concept, but maybe it's useful to someone.

只是一个概念证明,但也许对某人有用。

Note:there's a gotcha if one function that uses parse_argscalls another function that uses parse_argsandthe outer function declares e.g. local n4='', but the inner one doesn't and4 or more positional parameters are passed to the inner function

注意:如果一个使用的函数parse_args调用另一个使用的函数parse_args并且外部函数声明了 eg local n4='',则有一个问题,但内部函数没有,并且将4 个或更多位置参数传递给内部函数

回答by Vlad

Just mashed up a quickie, which easily handles a mixture of options and positional-parameters (leaving only positional-params in $@):

刚刚混搭了一个 quickie,它可以轻松处理选项和位置参数的混合(仅在 $@ 中留下位置参数):

#!/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+=("")                                           ;;
    *) echo "Coding error: '-$arg' is not handled by case">&2 ;;
esac;shift;[ "" != "$OPTARG" ] && shift;done
[ ${#SET[@]} -gt 0 ] && set "" "${SET[@]}" && shift

echo -e "=========\nLeftover (positional) parameters (count=$#) are:"
for i in `seq $#`;do echo -e "\t$i> [${!i}]";done

Sample output:

示例输出:

[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:~]$

回答by user

Instead of using getopts, you can directly implement your own bash argument parser. Take this as a working example. It can handle simultaneously name and position arguments.

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

#!/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="";
        namedargument="";
        argumentvalue="";

        if [[ -z "${namedargument}" ]]; then
            printf "Error: Missing command line option for named argument '%s', got '%s'...\n" "${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'...\n" "${namedargument}" "${argumentvalue}";
                exit 1;
            fi;
        done;

        if [[ -n "${!variabletoset}" ]]; then
            printf "Warning: Overriding the named argument '%s=%s' with '%s'...\n" "${namedargument}" "${!variabletoset}" "${argumentvalue}";
        else
            printf "Setting '%s' named argument '%s=%s'...\n" "${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="";
        argumentvalue="";

        if [[ -z "" ]];
        then
            argumentvalue="";
        fi;

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

    function validateposisionaloption() {
        local variabletoset;
        local argumentvalue;

        variabletoset="";
        argumentvalue="";

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

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

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

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

            -s|--skip)
                validateintegeroption "" "";
                validateduplicateoptions g_installation_model_skip_commands "" "";
                shift;
                ;;

            -v|--version)
                validateduplicateoptions branch_or_tag "" "";
                shift;
                ;;

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

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

                    2)
                        validateintegeroption "";
                        validateposisionaloption g_installation_model_skip_commands "";
                        ;;

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

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

You would call this function as:

您可以将这个函数称为:

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

Usage example:

用法示例:

./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'...

References:

参考:

  1. example_installation_model.sh.md
  2. Checking for the correct number of arguments
  3. https://unix.stackexchange.com/questions/129391/passing-named-arguments-to-shell-scripts
  4. An example of how to use getopts in bash
  1. example_installation_model.sh.md
  2. 检查参数的正确数量
  3. https://unix.stackexchange.com/questions/129391/passing-named-arguments-to-shell-scripts
  4. 如何在 bash 中使用 getopts 的示例

回答by Yuriy Pozniak

You can try this trick: after while loop with optargs, just use this snippet

你可以试试这个技巧:在带有 optargs 的 while 循环之后,只需使用这个片段

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

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

POSITIONAL="$@"

However, this approach has a bug:

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

    all the options after the first positional argument are ingored by getopts and are considered as positional arguments - event those that are correct (see sample output: -m and -c are among positional arguments)
    第一个位置参数之后的所有选项都由 getopts 分析并被视为位置参数 - 事件那些是正确的(参见示例输出:-m 和 -c 在位置参数中)

Maybe it has even more bugs...

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

Look at the whole example:

看整个例子:

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"

Output:

输出:

[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

回答by Henk Langeveld

There are some standards for unix option processing, and in shell programming, getoptsis the best way of enforcing them. Almost any modern language (perl, python) has a variant on getopts.

unix 选项处理有一些标准,在 shell 编程中,这getopts是强制执行它们的最佳方式。几乎所有现代语言(perl、python)在getopts.

This is just a quick example:

这只是一个简单的例子:

command [ options ] [--] [ words ]
  1. Each option must start with a dash, -, and must consist of a single character.

  2. The GNU project introduced Long Options, starting with two dashes --, followed by a whole word, --long_option. The AST KSH project has a getopts that also supports long options, andlong options starting with a single dash, -, as in find(1).

  3. Options may or may not expect arguments.

  4. Any word not starting with a dash, -, will end option processing.

  5. The string --must be skipped and will end option processing.

  6. Any remaining arguments are left as positional parameters.

  1. 每个选项必须以破折号、 开头-,并且必须由单个字符组成。

  2. GNU 项目引入了长选项,以两个破折号开头--,后跟一个完整的单词--long_option. AST KSH 项目有一个 getopts,它也支持长选项,以及以单个破折号开头的长选项-,如find(1).

  3. 选项可能需要也可能不需要参数。

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

  5. --必须跳过该字符串并将结束选项处理。

  6. 任何剩余的参数都保留为位置参数。

The Open Group has a section on Utility Argument Syntax

开放组有一个部分关于 Utility Argument Syntax

Eric Raymond's The Art of Unix Programming has a chapteron traditional unix choices for option letters and their meaning.

Eric Raymond 的 The Art of Unix Programming有一章关于选项字母的传统 unix 选择及其含义。