5

Suppose one has the following case:

#!/bin/sh

case $1 in

e|ex|exa|exam|examp|exampl|example) echo "OK"
;;
t|te|tes|test) echo "Also OK"
;;
*) echo "Error!"
;;

esac

Is there a more elegant and at the same time POSIX-compliant solution (i.e., no bash, zsh, etc.) to a situation like this?

P.S. No need for exampleeee or Exam to work.

user464484
  • 53
  • 4
  • 1
    Can you describe the situation, otherwise it would be easy to say "use `e*)` instead". – Kusalananda Mar 31 '21 at 17:43
  • @Kusalananda I guess that the OP wants `e` to work, and `ex` to work, and `examp` to work, but not `e2` or `ea` or `exemplary` etc. It would be really helpful if you could clarify that though, user464484. – terdon Mar 31 '21 at 17:50
  • @Kusalananda I'd like to have particular shortcuts. If I would use `e*`, a a user could just type any other word with an 'e' at the beginning which could lead to some problems in comprehending the script's output. – user464484 Mar 31 '21 at 17:53
  • @terdon Exactly! – user464484 Mar 31 '21 at 17:54
  • OK, but please [edit] your script and clarify. We need to know exactly what you require. For instance, should `exampleeee` also work? Should `E` work? Or `Exam`? Comments are hard to read, easy to miss and can be deleted without warning so it is important to have all relevant information in the actual question. – terdon Mar 31 '21 at 17:57
  • Also, does this _need_ to be `sh` or are you open to more sophisticated shells like `bash` or `zsh` etc? I see you've tagged with `posix`, so we should stick to POSIX `sh`, right? – terdon Mar 31 '21 at 18:00
  • Do you want to explicitly _not_ match `exampl`, as with your current code in the question? – Kusalananda Mar 31 '21 at 18:07
  • @Kusalananda No `exampl` is also included. I just made a mistake; which is another indication of how inelegant the current solution is. – user464484 Mar 31 '21 at 18:11
  • @user464484 One could also say that it's elegant in the way it allows you to pick _exactly_ the strings you want to match, e.g. `e`, `ex`, and `example`, but no other string. – Kusalananda Mar 31 '21 at 18:12
  • Are you trying to re-implement GNU `getopt`? – Kusalananda Mar 31 '21 at 18:19
  • @Kusalananda No. Are you telling me that there is an easier way to achieve this with it? – user464484 Mar 31 '21 at 18:30

2 Answers2

5

What you can do is turn the comparison around:

case "example" in
  "$1"*) echo OK ;;
  *) echo Error ;;
esac

With multiple words, you can stick with your original idea

case "$1" in
  e|ex|exa|exam|examp|exampl|example) : ;;
  t|te|tes|test) : ;;
  f|fo|foo) : ;;
  *) echo error ;;
esac

or use a loop and a "boolean" variable

match=""

for word in example test foo; do
  case "$word" in
    "$1"*) match=$word; break ;;
  esac
done

if [ -n "$match" ]; then
  echo "$1 matches $match"
else
  echo Error
fi

You can decide which is better. I think the first one is elegant.

glenn jackman
  • 84,176
  • 15
  • 116
  • 168
  • What to do if I have more than one option? – user464484 Mar 31 '21 at 18:07
  • @user464484 What's an "option" in this case? Please update your question with relevant information. – Kusalananda Mar 31 '21 at 18:13
  • @Kusalananda Done! – user464484 Mar 31 '21 at 18:16
  • 3
    An improvement that could be made would be to check for an input that matches more than one of the words. E.g. if the word `exit` was added to the list, then `ex` could be either it or `example`. Also the empty string matches everything. Here, an ambiguous input would just return the first match. You could add a test inside the `case` to see if `$match` was already set, and drop an error if so. – ilkkachu Mar 31 '21 at 18:57
2

You mentioned "option" in a comment, which made me think maybe you are trying to parse command line options. Since the POSIX getopts utility can't parse "long options", like --test or --example, we can ask GNU getopt to parse them for us instead.

The following is a shell script that takes the short options -e and -t, and the corresponding "long options" --example and --test. The long options may be specified on the command line as any prefix string of the full option string, e.g. --e, --ex etc. would all resolve to --example. In the code below, the --test/-t option takes a mandatory argument, signified by the trailing : after the corresponding long and short option string.

GNU getopt (part of util-linux) is used for parsing the command line.

#!/bin/sh

opts=$( getopt -o et: -l example,test: -- "$@" )
if [ "$?" -ne 0 ]; then
    echo 'Error in getopt' >&2
    exit 1
fi

eval set -- "$opts"
unset opts

unset testarg
while true; do
    case "$1" in
        -e|--example)
            echo 'Example option'
            shift
            ;;
        -t|--test)
            echo 'Test option'
            testarg=$2
            shift 2
            ;;
        --)
            shift
            break
            ;;
        *)
            echo 'Command line parsing error' >&2
            exit 1
    esac
done

if [ -n "$testarg" ]; then
        printf 'Test argument = "%s"\n' "$testarg"
fi

Testing:

$ ./script -e
Example option
$ ./script --exa
Example option
$ ./script --exa -t hello
Example option
Test option
Test argument = "hello"
$ ./script --exa --te='hello world'
Example option
Test option
Test argument = "hello world"
$ ./script -et 'hello world'
Example option
Test option
Test argument = "hello world"
$ ./script -eethello
Example option
Example option
Test option
Test argument = "hello"
Kusalananda
  • 320,670
  • 36
  • 633
  • 936