I wrote a bash script that takes a command as the first positional parameter and uses a case construct as a dispatch similar to the following:
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
This script decides what function to call based on $1
and then passes the remaining arguments to that function. I would like to add the ability dispatch on distinct partial matches, but I want to do it in an elegant way (elegant defined as a way that is both easy to read and is not so verbose as to be an eyesore or a distraction).
The obvious functioning (but not elegant) solution might be something like this:
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
As you can see, explicitly enumerating all distinct permutations of each command name can get really messy.
For a moment, I thought it might be alright to use a wildcard match like this:
case $cmd in
com*)
do_command $*
;;
cop*)
do_copy $*
;;
i*)
do_imperative $*
;;
*)
echo "Usage: $0 [ command | copy | imperative ]" >&2
;;
esac
This is less of an eyesore. However, this could result in undesirable behavior such as where do_command
is called when $1
is given as "comblah" or something else that shouldn't be recognized as a valid argument.
My question is: What is the most elegant (as defined above) way to correctly dispatch such a command where the user can provide any distinct truncated form of the expected commands?
It seems that a few of you like the idea of using a resolver to find the full command match prior to the dispatching logic. That's might just be the best way to go for large command sets or sets that have long words. I put together the following hacked up mess--it makes 2 passes using built-in parameter expansion substring removal. I seems to work well enough and it keeps the dispatch logic clean of the distraction of resolving partial commands. My bash version is 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"