0

I'm using getopts and case...esac for the first time. I've seen tutorials and questions with many different patterns for the default/catch-all option: ?, *, \?, [?]. They all seem to function the same, as you can see if you change their order in this script:

#!/bin/bash
set -e
set -u
set -o pipefail

while getopts ":d:l:h:" opt; do
    case $opt in
        d)  echo getopts saw $opt with value $OPTARG ;;
        l)  echo getopts saw $opt with value $OPTARG ;;
        h)  echo getopts saw $opt with value $OPTARG ;;
        :)  echo "getopts saw $opt with value $OPTARG and went to :" ;;
        *)  echo "getopts saw $opt with value $OPTARG and went to *" ;;
        ?)  echo "getopts saw $opt with value $OPTARG and went to ?" ;;
        \?) echo "getopts saw $opt with value $OPTARG and went to \?" ;;
        [?]) echo "getopts saw $opt with value $OPTARG and went to [?]" ;;
    esac
done
echo script done

Running the code below will always stop at the first matching option in the case list:

$ ./test.sh -c
getopts saw ? with value c and went to *
script done

Are all of these exactly the same?

In response to @BenjaminW.'s comment: The manual quote below explains that getopts will return ? vs : depending on whether getopts is set to silent. So my modified questions are:

  • Are ?, \?, and [?]equivalent?
  • Is the * unnecessary?

From the manual: If a required argument is not found, and getopts is not silent, a question mark (‘?’) is placed in name, OPTARG is unset, and a diagnostic message is printed. If getopts is silent, then a colon (‘:’) is placed in name and OPTARG is set to the option character found.

Josh
  • 1,210
  • 12
  • 30
  • I'm reading this https://stackoverflow.com/questions/11279423/bash-getopts-with-multiple-and-mandatory-options/11279500#11279500 to see if it has the answer, but I don't understand what an "unimplemented option" is. – Josh Apr 07 '20 at 21:28
  • My answer would more or less quote the [manual](https://www.gnu.org/software/bash/manual/bash.html#index-getopts), did you go through that paragraph? – Benjamin W. Apr 07 '20 at 21:37

4 Answers4

5

Only * is a default for case.

The reason the others work in this script is because you're processing getopts results. It sets $opt to ? for any option that isn't listed in the getopts argument, and you already handle all those options explicitly.

But if you were processing more general data, you wouldn't be able to use ? as the default case.

Barmar
  • 741,623
  • 53
  • 500
  • 612
4

Are all of these exactly the same?

No. These are the same:

        \?)  echo "getopts saw $opt with value $OPTARG and went to ?" ;;
        [?]) echo "getopts saw $opt with value $OPTARG and went to \?" ;;

Each of those patterns matches a string consisting of a single literal question mark character. That is to be distinguished from this ...

        ?)  echo "getopts saw $opt with value $OPTARG and went to ?" ;;

... which matches strings consisting of exactly one character, any character. And that is again different from ...

        *)  echo "getopts saw $opt with value $OPTARG and went to *" ;;

... which matches any string, of any length, including zero.

However, all of these alternatives have the same effect in your particular code, because if getopt sees an option character that is not in the option string, then it sets the designated variable to a single question mark. All of the aforementioned patterns match that, and you have other, earlier cases for all option letters in the option string you are using.

The only true catch-all pattern for a case statement is *.

John Bollinger
  • 160,171
  • 8
  • 81
  • 157
1

They mean different things (except for two which are the same):

  • \? only matches a question mark: ?
  • [?] also only matches a question mark.
  • ? matches any single character, like a, :, P and ?
  • * matches any number of any character, like a, apple, ?, :centipede== and the empty string.

When getopts processes an option it doesn't recognize, it sets opt to a literal question mark ?. You will notice that while the patterns match different things, they all share ? as a possible match. They therefore all do the job in your specific case.

However, the best way is to use \? or [?] to match the case of an unsupported option, and additionally use * to catch any options/values that you didn't account for:

#!/bin/bash
set -e
set -u
set -o pipefail

while getopts ":d:l:h:x" opt; do
    case $opt in
        d)  echo getopts saw $opt with value $OPTARG ;;
        l)  echo getopts saw $opt with value $OPTARG ;;
        h)  echo getopts saw $opt with value $OPTARG ;;
        :)  echo "getopts saw $opt with value $OPTARG and went to :" ;;
        \?) echo "getopts option not recognized" ;;
        *)  echo >&2 "Oops, unhandled $opt! Please file a bug!"
            exit 1
           ;;
    esac
done
echo script done

This way, flags you claim to handle but don't, like the -x added here, or code bugs that trash the opt variable will be correctly flagged as a bug in the script, and not reported as a user error.

that other guy
  • 116,971
  • 11
  • 170
  • 194
0

put case with * to the end, because single or multiple character matches this option.

Saboteur
  • 1,331
  • 5
  • 12