Bash命令行解析维护选项的引用

时间:2017-12-04 14:46:57

标签: bash shell unix

在不介绍getopts的情况下,我想像这样解析表达式:

./cli.sh data i -f -f="./path/to/file.txt" --flags="--a --b"

其变体/组合:

./cli.sh -file="./path/to/file.txt" data --flags="--a --b" -f i

不幸的是,我很难阅读所提交的长期选项的所有可选值,例如:--flags="--a --b"仅返回--a作为长选项--flags的值。

我创建了一个解析器的最小化版本,它展示了我当前的问题:

#!/usr/bin/env bash

__init_system () {
    CMD_SED="sed"
    CMD_SED_EXT="sed -E"
}

__init_system_Darwin () {
    __init_system
    CMD_SED="gsed"
    CMD_SED_EXT="gsed -E"
}

invoke_func () {
    SYSTEM=$(uname)
    FUNC=$1 && shift 1
    declare -f ${FUNC}_${SYSTEM} >/dev/null
    [ $? -eq 0 ] && { ${FUNC}_${SYSTEM} "$@"; return $?; } || { ${FUNC} "$@"; return $?; }
}

handle_cli_harmonizing () {
    cli_adjusted=$(echo "$@" | ${CMD_SED_EXT} \
                    -e 's@([ ])+@ @g'\
                    -e 's@\<h\>@help@g'\
                    -e 's@\<e\>@enable@g'\
                    -e 's@\<d\>@disable@g'\
                    -e 's@\<i\>@import@g'\
                    -e 's@\<sl\>@showlog@g'\
                    -e 's@\<st\>@status@g'\
                    | tr ' ' '\n' | sort -u | xargs
                )
    #echo ">>>  Original: <$@>"
    #echo ">>>  Adjusted: <$cli_adjusted>"
}

handle_cli_parsing () {
    for param in $@; do
        case ${param} in
            start|stop|status|enable|disable|data|showlog)
                CMD=$param
                ;;
            help|app|stats|import|export|dbinfo|exec)
                SPEC=$param
                ;;
            --*|-*)
                #echo ">>> Found param: <$param>"
                OPT="$OPT $param"
                ;;
            *)
                echo ">>> Parsing mismatch: $param"
                ;;
        esac
    done
}

handle_cli_optparams () {
    for opt in $OPT; do
        case "$opt" in
            --force|-f)
                OPT_FORCE=1
                ;;
            --file=*|-f=*)
                OPT_FILE=1
                FILE=${opt#*=}
                ;;
            --flags=*)
                OPT_FLAGS=1
                FLAGS=${opt#*=}
                ;;
            *)
                echo ">>> Unknown/unimplemented option specifier: $opt"
                ;;
        esac
    done
}

invoke_func __init_system
invoke_func handle_cli_harmonizing "$@"
invoke_func handle_cli_parsing ${cli_adjusted}
invoke_func handle_cli_optparams

echo "DEBUG[   CLI]: CMD=$CMD SPEC=$SPEC"
echo "DEBUG[TOGGLE]: FORCE=$OPT_FORCE FILE=$OPT_FILE FLAGS=$OPT_FLAGS"
echo "DEBUG[  OPTS]: FILE=$FILE FLAGS=$FLAGS"

预期的输出:

$ ./cli.sh data i -f -f="./path/to/file.txt" --flags="--a --b"
DEBUG[   CLI]: CMD=data SPEC=import
DEBUG[TOGGLE]: FORCE=1 FILE=1 FLAGS=1
DEBUG[  OPTS]: FILE=./path/to/file.txt FLAGS=--a --b

当前输出:

$ ./cli.sh data i -f -f="./path/to/file.txt" --flags="--a --b"
>>> Unknown/unimplemented option specifier: --b
DEBUG[   CLI]: CMD=data SPEC=import
DEBUG[TOGGLE]: FORCE=1 FILE=1 FLAGS=1
DEBUG[  OPTS]: FILE=./path/to/file.txt FLAGS=--a

这可能是一些引用问题或遗失set,但我似乎被困在这里,而且我真的更喜欢保持我采取的简单方法。

如果有人另外提供仅限shell(没有bashims)版本的便携性问题,我会特别放养,但这是锦上添花。请注意,这只是我编写的整个解析器中非常简单的部分;足以展示我目前的挑战。

在撰写此问题时,我已阅读了所有建议的相关问题,我在How do I parse command line arguments in Bash?上思考了一段时间。

2 个答案:

答案 0 :(得分:0)

问题的根源在于:

OPT="$OPT $param"

这是$param中嵌入的空格与用于附加到$OPT末尾的空间无法区分的地方。

您可以通过将OPT数组添加到数组中来正确地附加值来避免此问题:

OPT+=("$param")

...然后使用数组语法迭代数组的值:

for opt in "${OPT[@]}"; do

...等等,始终如一,无处不在,并注意正确的双引号。

答案 1 :(得分:0)

即使经过多年的遗弃,我也想描述上面提出的问题的解决方案。

尽管janos为我提供了指针,但该解决方案实际上要简单得多,并且可以保持预期的原始灵活性:

#!/usr/bin/env bash

handle_cli_parsing () {
    while [ $# -ne 0 ]; do
        param=$1
        [ x"$param" = x"sl" ] && param=showlog
        [ x"$param" = x"sh" ] && param=showlog
        [ x"$param" = x"h" ]  && param=help
        [ x"$param" = x"i" ]  && param=import
        [ x"$param" = x"e" ]  && param=enable
        [ x"$param" = x"d" ]  && param=disable
        case ${param} in
            start|stop|status|enable|disable|data|showlog)
                CMD=$param
                ;;
            help|app|stats|import|export|dbinfo|exec)
                SPEC=$param
                ;;
            --force|-f)
                OPT_FORCE=1
                ;;
            --file=*|-f=*)
                OPT_FILE=1
                FILE=${param#*=}
                ;;
            --flags=*)
                OPT_FLAGS=1
                FLAGS=${param#*=}
                ;;
            --*|-*)
                echo ">>> New param: <$param>"
                ;;
            *)
                echo ">>> Parsing mismatch: $param"
                ;;
        esac
        shift 1
    done
}

handle_cli_parsing "$@"

echo "DEBUG[   CLI]: CMD=$CMD SPEC=$SPEC"
echo "DEBUG[TOGGLE]: FORCE=$OPT_FORCE FILE=$OPT_FILE FLAGS=$OPT_FLAGS"
echo "DEBUG[  OPTS]: FILE=$FILE FLAGS=$FLAGS"

现在输出符合预期,并且解析具有预期的灵活性:

$ ./cli.sh data i -f -f="./path/to/file.txt" --flags="--a --b"
DEBUG[   CLI]: CMD=data SPEC=import
DEBUG[TOGGLE]: FORCE=1 FILE=1 FLAGS=1
DEBUG[  OPTS]: FILE=./path/to/file.txt FLAGS=--a --b