user1874594 user1874594 - 25 days ago 9
Bash Question

How to use mutually exclusive flags in your shell and add an optional argument flag ( stuck with getopts)

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

hvd hvd
Answer

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".