82

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

如果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

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

test.sh param1 param2 param3

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

4

9 回答 9

112

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

本质上,如果您想做类似的事情:

script.sh [options] ARG1 ARG2

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

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

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

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

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

希望有帮助!!

于 2012-11-15T15:04:13.007 回答
27

混合 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} $1 "
   shift
done

echo "ARGS : $ARGS"
exit 1

结果:

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
于 2016-10-22T10:20:30.057 回答
23

myshell.sh:

#!/bin/bash

script_args=()
while [ $OPTIND -le "$#" ]
do
    if getopts h:d:s: option
    then
        case $option
        in
            h) host_name="$OPTARG";;
            d) wait_time="$OPTARG";;
            s) script="$OPTARG";;
        esac
    else
        script_args+=("${!OPTIND}")
        ((OPTIND++))
    fi
done

"$script" "${script_args[@]}"

测试.sh:

#!/bin/bash
echo "$0 $@"

测试 OP 的案例:

$ PATH+=:.  # Use the cases as written without prepending ./ to the scripts
$ myshell.sh -h hostname -s test.sh -d waittime param1 param2 param3
./test.sh param1 param2 param3
$ myshell.sh param1 param2 -h hostname param3 -d waittime -s test.sh
./test.sh param1 param2 param3
$ myshell.sh param1 -h hostname -d waittime -s test.sh param2 param3
./test.sh param1 param2 param3

这是怎么回事:

getopts如果遇到位置参数将失败。如果将其用作循环条件,则只要位置参数出现在选项之前,循环就会提前中断,就像在两个测试用例中所做的那样。

因此,只有在处理完所有参数后,此循环才会中断。如果getopts无法识别某些东西,我们只是假设它是一个位置参数,并在手动递增getopts计数器的同时将其填充到一个数组中。

可能的改进:

如所写,子脚本不能接受选项(仅位置参数),因为getopts在包装脚本中会吃掉这些选项并打印错误消息,同时将任何参数视为位置参数:

$ myshell.sh param1 param2 -h hostname -d waittime -s test.sh -a opt1 param3
./myshell.sh: illegal option -- a
./test.sh param1 param2 opt1 param3

如果我们知道子脚本只能接受位置参数,那么myshell.sh可能应该在无法识别的选项上停止。这可以像在case块末尾添加默认的最后一个案例一样简单:

            \?) exit 1;;
$ myshell.sh param1 param2 -h hostname -d waittime -s test.sh -a opt1 param3
./myshell.sh: illegal option -- a

如果子脚本需要接受选项(只要它们不与 中的选项冲突myshell.sh),我们可以getopts通过在选项字符串前面加上冒号来切换到静默错误报告:

    if getopts :h:d:s: option

然后我们将使用默认的最后一种情况将任何无法识别的选项填充到script_args

            \?) script_args+=("-$OPTARG");;
$ myshell.sh param1 param2 -h hostname -d waittime -s test.sh -a opt1 param3
./test.sh param1 param2 -a opt1 param3
于 2020-08-15T00:08:12.207 回答
3

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

像其他人一样将 param1-3 放入选项中会更好。

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

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

于 2012-07-31T15:21:19.687 回答
2

我想出了一种getopts可以扩展到真正混合选项和位置参数的方法。getopts这个想法是在调用和分配找到的任何位置参数之间交替n1n2等等n3

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 的情况下,您可以像这样使用它:

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

运行它显示输出:

$ 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 并且外部函数声明了 eg local n4='',但内部函数没有声明并且4 个或更多位置参数被传递给内部函数,则存在一个问题

于 2014-08-31T20:48:48.753 回答
1

只是混搭了一个 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+=("$1")                                           ;;
    *) 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

样本输出:

[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:~]$
于 2015-08-03T02:41:52.403 回答
0

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="${1}";
        namedargument="${2}";
        argumentvalue="${3}";

        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="${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'...\\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="${1}";
        argumentvalue="${2}";

        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 ${1} in
            -y|--yes)
                yes_to_all_questions="${1}";
                printf "Named argument '%s' for yes to all questions was triggered.\\n" "${1}";
                ;;

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

            -h|--help)
                printf "Print help here\\n";
                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.\\n" "${1}";
                        exit 1;
                        ;;
                esac;
                ;;
        esac;
        shift;
    done;

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

您可以将此函数称为:

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

使用示例:

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

参考:

  1. example_installation_model.sh.md
  2. 检查正确数量的参数
  3. https://unix.stackexchange.com/questions/129391/passing-named-arguments-to-shell-scripts
  4. 如何在 bash 中使用 getopts 的示例
于 2019-07-06T05:46:45.333 回答
-1

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

这只是一个简单的例子:

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

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

  3. 选项可能会或可能不会期望参数。

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

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

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

开放组有一个部分Utility Argument Syntax

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

于 2012-07-31T22:30:20.047 回答
-1

您可以尝试这个技巧:在使用 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="$@"

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

    第一个位置参数之后的所有选项都由 getopts 输入并被视为位置参数 - 事件那些正确的(参见示例输出:-m 和 -c 在位置参数中)

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

查看整个示例:

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"

输出:

[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
于 2017-02-14T10:38:30.457 回答