3

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

11
  • 1
    Why are you treating SID an an option argument? If you simply make -a and -b options that do not take any arguments (by dropping the :), and always check whether a non-option was supplied as the SID, this would already work as expected.
    – user743382
    Commented Feb 11, 2014 at 8:18
  • 1
    Updated the post with those results. Still stuck Commented Feb 11, 2014 at 16:52
  • 1
    You do need that the first and third line of that last part (shift $(($OPTIND - 1)) / [ "$SID" ] || die "You must, at the minimum, supply SID") that you've now turned into comments, in order to reject -a by itself. I'll expand my approach into an answer in a short while.
    – user743382
    Commented Feb 11, 2014 at 18:01
  • 1
    I actually want to add an additional -p /path flag. I updated my Q.Whenever you get a chance.Thx Commented Feb 14, 2014 at 20:57
  • 1
    That's something that you should probably ask as a new question, not as an edit of your old question. But, unlike your -a/-b, that does seem to me like it would work well as an option argument (:abp:, not :ab:p). In your p) case, just set mypath=$OPTARG (and validate as needed). The rest of the checks would remain the same, because you would still only have one non-option argument. However, while -p /path -a 123 seems clearly valid and would be allowed, so would -a -p /path 123, yet -a 123 -p /path would be rejected. You can decide if that makes sense to you.
    – user743382
    Commented Feb 15, 2014 at 6:40

3 Answers 3

5

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 }
3

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

4
  • Thanks for the clarifications. The exit 1 kicks me out of the shell : ( !!. If I use return it behaves wonky Commented Feb 11, 2014 at 20:03
  • @user1874594 Right, that's because you're calling it with . instead of a separate shell (as is more common for scripts like this). return has no meaningful effect here, since you ignore the exit status of the die function. It would work if you call it as ksh opt.ksh [args], and it's a bit more complicated and beyond what I think your question is really about to get it working properly for the . form too.
    – user743382
    Commented Feb 11, 2014 at 20:31
  • TY. So I'd just +x it and exe it directly then. Let me try and update TY again Commented Feb 11, 2014 at 21:08
  • TY. So I'd just +x it and exe it directly then. Let me try and update TY again . Works like a charm. V helpful learning. TY Commented Feb 11, 2014 at 21:28
2

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.

1
  • 1
    Hi TY for that innovative approach. It still does not work. I changed the exit in die to return1 else it exiting my login .When I use return 1 the results are totally unpredictable. i upated them in the Q above Commented Feb 11, 2014 at 16:21

Not the answer you're looking for? Browse other questions tagged or ask your own question.