我编写了一个bash脚本,它将一个命令作为第一个位置参数,并使用一个case结构作为分派,如下所示:
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决定调用哪个函数,然后将剩余的参数传递给该函数。我想在不同的部分匹配上添加能力调度,但我想以一种优雅的方式来做(优雅的定义是一种既易于阅读又不会冗长到有碍观瞻或分散注意力的方式)。
显而易见的功能(但不优雅)的解决方案可能是这样的:
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正如您所看到的,显式枚举每个命令名的所有不同排列可能会变得非常混乱。
有那么一阵子,我想像这样使用通配符匹配可能没问题:
case $cmd in
com*)
do_command $*
;;
cop*)
do_copy $*
;;
i*)
do_imperative $*
;;
*)
echo "Usage: $0 [ command | copy | imperative ]" >&2
;;
esac这可不是什么难看的事情。然而,这可能会导致不受欢迎的行为,比如当$1被指定为"comblah“时,调用do_command的地方,或者其他不应该被识别为有效参数的地方。
我的问题是:在用户可以提供预期命令的任何截断形式的情况下,正确调度此类命令的最优雅(如上所述)方法是什么?
发布于 2013-02-16 22:48:58
Bash中命令调度的模式匹配
似乎你们中的一些人喜欢在调度逻辑之前使用解析器来查找完整的命令匹配的想法。对于大型命令集或具有长单词的集,这可能是最好的方法。我整理了下面这些乱七八糟的东西--它使用内置的参数扩展子串删除进行了2次遍历。我看起来工作得很好,而且它保持了分派逻辑的清晰度,避免了解析部分命令的干扰。我的bash版本是4.1.5。
#!/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"发布于 2013-02-16 18:51:38
我想出了以下解决方案,它应该适用于任何与bourne兼容的shell:
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的源代码并不美观,但我希望您在大多数时候都不必查看这些代码。
发布于 2018-08-26 18:33:49
更新1:
使用(partial)命令作为第一个位置参数调用match,后跟要测试的字符串。在多个匹配中,每个部分匹配都将以大写字母进行提示。
# @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将需要一些逻辑来解析部分命令。(类似于我的第一个答案)
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方法;在使用之前需要一些逻辑,以解决消除部分匹配的歧义。
#!/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;i,im,imp up直到命令式匹配。shift会将第二个位置参数设置为first,这意味着如果脚本被调用为:
./script.sh imp say hello将解析为
do_imperative say hello如果您希望进一步解析速记命令,也可以在函数中使用相同的方法。
https://stackoverflow.com/questions/14908958
复制相似问题