Question

I am using a standard getopts logic. But I want to how I can make the options I offer- mutually exclusive. e.g.

shell.sh -a SID 
                              <accepted>
shell.sh -b SID
                              <accepted>
shell.sh -ab SID 
               Message- using ab together is the same as running shell.sh     without   any   options supplying  just SID . Help usage < ya da ya > 
shell.sh 
                Please enter SID at the minimum. Usage < ya da ya >
shell.sh SID
               <accepted>

I am trying to develop this logic using something like below

while getopts ":a:b:" opt; do
  case $opt in
  a ) SID="$OPTARG";;
      set var=1
  b ) SID="$OPTARG";;
      set var=2

 \?) echo "Invalid option: -"$OPTARG"" >&2
        exit 1;;
  ) echo "Option -"$OPTARG" requires an argument." >&2
        exit 1;;
  esac
done

If  (( val == 1 ))  then ; # option a is invoked SID supplied 
<stuff>
elif  (( val == 2 )) then ; # option b is invoked SID supplied 

<stuff>

else # SID supplied but neither a or b is invoked 
<stuff>
fi

How do enforce mutually exclusive flags. I a sure there are more acrobat ways to do it. I think I am missing something commonsense here - and trying to figure that out . Thx

$ more opt.ksh
die () {
    echo "ERROR: $*. Aborting." >&2
    return  1
}

var=
while getopts ":a:b:" opt; do
  case $opt in
      a ) SID="$OPTARG"
          [ "$var" = 2 ] && die "Cannot specify option a after specifying option b"
          [ "$OPTARG" = b ] && die "Do not specify b as a value for option a"
          var=1
          ;;
      b ) SID="$OPTARG"
          [ "$var" = 1 ] && die "Cannot specify option b after specifying option a"
          [ "$OPTARG" = a ] && die "Do not specify a as a value for option b"
          var=2
          ;;
      :)  die "Must supply an argument to $OPTARG"
          ;;
      \?) die "Invalid option: -$OPTARG. Abort"
          ;;
  esac
done
shift $(($OPTIND - 1))
[ "$SID" ] || SID=$1
[ "$SID" ] || die "You must, at the minimum, supply SID"

I am using ksh

$ ps -p $$
  PID TTY          TIME CMD
 1261 pts/45   00:00:00 ksh

1st time I run it .

$ . opt.ksh -a 123 -b 123  # c0
ERROR: Cannot specify option b after specifying option a. Aborting.
-bash: /home/d1ecom1/gin1: No such file or directory


$ . opt.ksh -ab 123  # c1 should reject "cant use a and b togather. try with a or b"
-nologin: .[25]: shift: 4: bad number
$ . opt.ksh -a -b  # c2  same as c1's message
$ . opt.ksh -a -b 123 # c3 same as c1

$ . opt.ksh -a -b 123  # c5 
$ . opt.ksh -ab 123    # c6
$ . opt.ksh -a 123 -b 123  # c7

All above cases C0:C7 should reject. Notice C0 and C7 are the same. Yet inspite of this C0 gives the expected error and C7 will not give any error ? strange

only ones to work should be

. opt.ksh -a 123
. opt.ksh -b 123
. opt.ksh  123

@hvd :TYSM for your reply. I would like to add an additonal flag -p that will give a "path override" option. so maybe we have to extend getopt to take parameters like this

    die () {
    echo "ERROR: $*. Aborting." >&2
    exit 1
}

var=
opta=false
optb=false
while getopts ":ab:p" opt; do
  case $opt in
      a ) $optb && die "Cannot specify option a after specifying option b"
          opta=true
          ;;
      b ) $opta && die "Cannot specify option b after specifying option a"
          optb=true
          ;;
      p )  [ -d "$mypath" ] && die "path is invalid"

          ;;

      \?) die "Invalid option: -$OPTARG. Abort"
          ;;
  esac
done
shift $(($OPTIND - 1))
test $# -eq 0 && die "You must supply SID"
test $# -eq 1 || die "Too many command-line arguments"
# SID=$1

The above was the old approach. Now I have 2 Input parameters. One is the SID and the other is the path. Is it as simple as the above or do I need to add more checks to prevent other unwanted combinations. The question I guess I am trying to aim at is , what more provisions would need to be made to allow this -p parameter which is an optiona override parameter. - p can co-exist with any parameter above but then I guess one requirement is that it should be immediately following the -p flag so this should not allow - because its not clear .

shell.sh -b -p 123 /home/yadaya

Thanks again

Was it helpful?

Solution

Here's how you can do it without making SID an option argument, which makes more sense to me:

die () {
    echo "ERROR: $*. Aborting." >&2
    exit 1
}

var=
opta=false
optb=false
while getopts ":ab" opt; do
  case $opt in
      a ) $optb && die "Cannot specify option a after specifying option b"
          opta=true
          ;;
      b ) $opta && die "Cannot specify option b after specifying option a"
          optb=true
          ;;
      \?) die "Invalid option: -$OPTARG. Abort"
          ;;
  esac
done
shift $(($OPTIND - 1))
test $# -eq 0 && die "You must supply SID"
test $# -eq 1 || die "Too many command-line arguments"
SID=$1

The changes I've made are:

  • return 1 becomes exit 1 (unrelated to your question, but die sounds like it shouldn't continue). return 1 would merely make the die function return non-successfully, but the calls to die don't check its result, it would carry on regardless.
  • The getopts string is :ab instead of :a:b:. No matter how you call your script, you always need to pass exactly one SID, regardless of options, so it doesn't make sense to me to include it as part of the options.
  • The options are stored in boolean opta and optb variables for easier checking
  • After all options are passed, the remaining command-line arguments are counted. Unless it's exactly one, the call to the script is invalid.

All your valid calls in the question are accepted, all the invalid ones are rejected. One note of interest about this, though: your test case c7 (-a 123 -b 123) is expected to fail, and does fail, but not because -a and -b are combined. Instead, in my approach, since -b appears after a non-option argument, it itself is a non-option argument, and the reason for rejecting it becomes "Too many command-line arguments".

OTHER TIPS

For a case where you must specify exactly one of options -s, -i or -h, and where you have two more options -v and -n which are optional, you could do something like the following:

modes=0
while getopts :vs:i:h:n opt; do
  case "$opt" in
    v)
      verbose='non-empty-string-true-flag'
      ;;
    s)
      ((modes++))
      : do something with OPTARG, the s form
      ;;
    i)
      ((modes++))
      : do something with OPTARG, the i form
      ;;
    h)
      ((modes++))
      : do something with OPTARG, the h form
      ;;
    n)
      dryrun='non-empty-string-true-flag'
      ;;
    :)
      printf 'Option -%s requires an argument\n' "$OPTARG" >&2
      usage
      exit 1
      ;;
    *)
      printf 'Invalid option: -%s\n' "$OPTARG" >&2
      usage
      exit 1
      ;;
  esac
done
shift "$((OPTIND-1))"
if [ "$modes" -eq 0 ]; then
  printf "Missing required option\n" >&2
  usage
  exit 1
elif [ "$modes" -gt 1 ]; then
  printf "Error: -h, -i and -s are mutually exclusive and may only be used once\n" >&2
  usage
  exit 1
fi

This is excerpted from a script of mine, with the processing to be done on the s, i or h arguments omitted for simplicity of illustration.

The synopsis for the script looks like:

$0 [ -v ] [ -n ] { -i IPADDRESS | -h HOSTNAME | -s HOSTSHA }

This makes options a and b mutually exclusive:

die () {
    echo "ERROR: $*. Aborting." >&2
    exit 1
}

var=
while getopts ":a:b:" opt; do
  case $opt in
      a ) SID="$OPTARG"
          [ "$var" = 2 ] && die "Cannot specify option a after specifying option b"
          [ "$OPTARG" = b ] && die "Do not specify b as a value for option a"
          var=1
          ;;
      b ) SID="$OPTARG"
          [ "$var" = 1 ] && die "Cannot specify option b after specifying option a"
          [ "$OPTARG" = a ] && die "Do not specify a as a value for option b"
          var=2
          ;;
      :)  die "Must supply an argument to $OPTARG"
          ;;
      \?) die "Invalid option: -$OPTARG. Abort"
          ;;
  esac
done
shift $(($OPTIND - 1))
[ "$SID" ] || SID=$1
[ "$SID" ] || die "You must, at the minimum, supply SID"

Note that optargs interprets shell.sh -ab SID as option a with argument b followed by SID as an argument to the script, not part of an option. To detect that problem, the lines above involving [ "$OPTARG" = b ] && die... and [ "$OPTARG" = a ] && die... were added.

You wanted to accept shell.sh SID with no option specified. This is handled by the first two lines after the case statement.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top