首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >bash:部分匹配,最多一个完整的大小写单词

bash:部分匹配,最多一个完整的大小写单词
EN

Stack Overflow用户
提问于 2013-02-16 17:46:43
回答 4查看 2.4K关注 0票数 4

我编写了一个bash脚本,它将一个命令作为第一个位置参数,并使用一个case结构作为分派,如下所示:

代码语言:javascript
复制
do_command() {
  # responds to invocation `$0 command ...`
}

do_copy() {
  # respond to invocation: `$0 copy...`
}

do_imperative() {
  # respond to invocation: `$0 imperative ...`
}

cmd=$1
shift
case $cmd in
command)
  do_command $*
  ;;
copy)
  do_copy $*
  ;;
imperative)
  do_imperative $*
  ;;
*)
  echo "Usage: $0 [ command | copy | imperative ]" >&2
  ;;
esac

该脚本根据$1决定调用哪个函数,然后将剩余的参数传递给该函数。我想在不同的部分匹配上添加能力调度,但我想以一种优雅的方式来做(优雅的定义是一种既易于阅读又不会冗长到有碍观瞻或分散注意力的方式)。

显而易见的功能(但不优雅)的解决方案可能是这样的:

代码语言:javascript
复制
case $cmd in
command|comman|comma|comm|com)
  do_command $*
  ;;
copy|cop)
  do_copy $*
  ;;
imperative|imperativ|imperati|imperat|impera|imper|impe|imp|im|i)
  do_imperative $*
  ;;
*)
  echo "Usage: $0 [ command | copy | imperative ]" >&2
  ;;
esac

正如您所看到的,显式枚举每个命令名的所有不同排列可能会变得非常混乱。

有那么一阵子,我想像这样使用通配符匹配可能没问题:

代码语言:javascript
复制
case $cmd in
com*)
  do_command $*
  ;;
cop*)
  do_copy $*
  ;;
i*)
  do_imperative $*
  ;;
*)
  echo "Usage: $0 [ command | copy | imperative ]" >&2
  ;;
esac

这可不是什么难看的事情。然而,这可能会导致不受欢迎的行为,比如当$1被指定为"comblah“时,调用do_command的地方,或者其他不应该被识别为有效参数的地方。

我的问题是:在用户可以提供预期命令的任何截断形式的情况下,正确调度此类命令的最优雅(如上所述)方法是什么?

EN

回答 4

Stack Overflow用户

回答已采纳

发布于 2013-02-16 22:48:58

Bash中命令调度的模式匹配

似乎你们中的一些人喜欢在调度逻辑之前使用解析器来查找完整的命令匹配的想法。对于大型命令集或具有长单词的集,这可能是最好的方法。我整理了下面这些乱七八糟的东西--它使用内置的参数扩展子串删除进行了2次遍历。我看起来工作得很好,而且它保持了分派逻辑的清晰度,避免了解析部分命令的干扰。我的bash版本是4.1.5。

代码语言:javascript
复制
#!/bin/bash
resolve_cmd() {
  local given=$1
  shift
  local list=($*)
  local inv=(${list[*]##${given}*})
  local OIFS=$IFS; IFS='|'; local pat="${inv[*]}"; IFS=$OIFS
  shopt -s extglob
  echo "${list[*]##+($pat)}"
  shopt -u extglob
}
valid_cmds="start stop status command copy imperative empathy emperor"

m=($(resolve_cmd $1 $valid_cmds))
if [ ${#m[*]} -gt 1 ]; then
  echo "$1 is ambiguous, possible matches: ${m[*]}" >&2
  exit 1
elif [ ${#m[*]} -lt 1 ]; then
  echo "$1 is not a recognized command." >&2
  exit 1
fi
echo "Matched command: $m"
票数 2
EN

Stack Overflow用户

发布于 2013-02-16 18:51:38

我想出了以下解决方案,它应该适用于任何与bourne兼容的shell:

代码语言:javascript
复制
disambiguate() {
    option="$1"
    shift
    found=""
    all=""
    comma=""
    for candidate in "$@"; do
        case "$candidate" in
            "$option"*)
                found="$candidate"
                all="$all$comma$candidate"
                comma=", "
        esac
    done    
    if [ -z "$found" ] ; then
        echo "Unknown option $option: should be one of $@" >&2
        return 1;
    fi
    if [ "$all" = "$found" ] ; then
        echo "$found"
    else
        echo "Ambigious option $option: may be $all" >&2
        return 1
    fi
}    
foo=$(disambiguate "$1" lorem ipsum dolor dollar)
if [ -z "$foo" ] ; then exit 1; fi
echo "$foo"

是的,disambiguate的源代码并不美观,但我希望您在大多数时候都不必查看这些代码。

票数 2
EN

Stack Overflow用户

发布于 2018-08-26 18:33:49

更新1:

使用(partial)命令作为第一个位置参数调用match,后跟要测试的字符串。在多个匹配中,每个部分匹配都将以大写字母进行提示。

代码语言:javascript
复制
# @pos 1 string
# @pos 2+ strings to compare against
# @ret true on one match, false on none|disambiguate match
match() {

    local w input="${1,,}" disa=();
    local len=${#input}; # needed for uppercase hints
    shift;

    for w in $*; do
        [[ "$input" == "$w" ]] && return 0;
        [[ "$w" == "$input"* ]] && disa+=($w);
    done

    if ! (( ${#disa[*]} == 1 )); then
        printf "Matches: "
        for w in ${disa[*]}; do
            printf "$( echo "${w:0:$len}" | tr '[:lower:]' '[:upper:]')${w:${len}} ";
        done
        echo "";
        return 1;
    fi

    return 0;
}

用法示例。可以调整match以打印/返回整个非歧义命令,否则do_something_with将需要一些逻辑来解析部分命令。(类似于我的第一个答案)

代码语言:javascript
复制
cmds="start stop status command copy imperative empathy emperor"

while true; do
    read -p"> " cmd
    test -z "$cmd" && exit 1;
    match $cmd $cmds && do_something_with "$cmd";
done

第一个答案: Case方法;在使用之前需要一些逻辑,以解决消除部分匹配的歧义。

代码语言:javascript
复制
#!/bin/bash
# script.sh

# set extended globbing, in most shells it's not set by default
shopt -s extglob;

do_imperative() {
    echo $*;
}

case $1 in
    i?(m?(p?(e?(r?(a?(t?(i?(v?(e))))))))))
        shift;
        do_imperative $*;
        ;;
    *)
        echo "Error: no match on $1";
        exit 1;
        ;;
esac

exit 0;

iimimp up直到命令式匹配。shift会将第二个位置参数设置为first,这意味着如果脚本被调用为:

代码语言:javascript
复制
./script.sh imp say hello

将解析为

代码语言:javascript
复制
do_imperative say hello

如果您希望进一步解析速记命令,也可以在函数中使用相同的方法。

票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/14908958

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档