0

I am struggling to make a script have this behavior:

  • I want to pass options like this ./script -ab is the same as ./script -a -b
  • And have this logic: "a & b -> c", "a & c -> c", "b & c -> c" (single option is function as normal)
  • Also I want this script to fall back to a function (e.g help function) when calling the script without any option

This is some example:

#!/usr/bin/env bash

if [[ ${#} -eq 0 ]]; then
    echo "Default function"
else
    while getopts ":a:b:c:" opt; do
        case ${opt} in
        a) echo "a" ;;
        b) echo "b" ;;
        c) echo "c" ;;
        *) echo -e "Invalid option: ${@}" ;;
        esac
    done
fi

Any help is appreciated

1
  • 2
    You've declared that all of your options require an argument (by writing a: instead of a), but in your examples you're not passing arguments. This is probably the source of most of your problems.
    – larsks
    Aug 10 at 13:12

2 Answers 2

2

It's probably most straightforward to just set flags for each of the options, and then after the loop check if the flags for both A and B are set and enable C if they are.

But you could model this as a bitmap with one bit each for A and B, and C being the state where both bits are set. So A=01, B=10, C=11 plus the default state with both bits cleared. (The states correspond to default=0, A=1, B=2, C=3 in decimal or hex.) Then just set the appropriate bits when the corresponding options are seen.

Like so:

#!/usr/bin/env bash

mode=0
while getopts "abc" opt; do
    case ${opt} in
        a) mode=$(( mode | 0x01 )) ;; # set rightmost bit
        b) mode=$(( mode | 0x02 )) ;; # set leftmost bit
        c) mode=$(( mode | 0x03 )) ;; # set both bits
        *) exit 1 ;;
    esac
done

echo "chosen mode is $mode (0 = default, 1 = a, 2 = b, 3 = c)"

Note that you mention -A and -b in the text, but your script has a and b. getopts cares about the letter case, so those aren't the same. Also the colons in getopts a:b:c: mark the options as taking arguments, but you don't seem to need that.

3
  • 1
    I don't know really, but you likely want to use 0x04 rather than 0x03 for c if you're aiming to use $mode as a bitmask, to capture the various combinations of the three options in the values 0 through to 7.
    – Kusalananda
    Aug 10 at 17:38
  • 1
    @Kusalananda, no, they said they want A+B to give C, and that happens with binary or if A and B are distinct bits and C is the combination of the two. I definitely don't want C to be a third bit because then it won't or together with either of the two others. (I can't edit the answer to show all the combinations right now, someone else may do it.)
    – ilkkachu
    Aug 11 at 18:11
  • Ah, I wasn't reading carefully enough. You're correct. I got too interested in the option processing and disregarded the required interaction between the options.
    – Kusalananda
    Aug 11 at 22:05
0

(Edited to handle new request in the question)

You're very close. Below is the kind of thing I've done. It looks a bit complex at first, but it's a sequence of steps setting the defaults, parsing the command line, checking for bad option values or combinations, then script main routine after:

#!/usr/bin/env bash

# defaults for options
help=n
opt_a=1
opt_b=production
opt_c=0

# if no options from my user, print help message only
if [[ $# -lt 1 ]]; then help=y; fi

# parse command line
while getopts ':ha:b:c' OPTION; do
  case "${OPTION}" in
     h) help=y            ;;
     a) opt_a="${OPTARG}" ;;
     b) opt_b="${OPTARG}" ;;
     c) opt_c=1           ;;
    \?) echo "$0: Error: Invalid option: -${OPTARG}" >&2 ; exit 1 ;;
     :) echo "$0: Error: option -${OPTARG} requires an argument" >&2 ; exit 1 ;;
  esac
done
shift $((OPTIND - 1))

# request for help overrides other actions
if [[ ${help} == 'y' ]]; then
  # put code here to print a USAGE message
  exit 0
fi

# check and error out on bad options from my user
# check -b:
case "$opt_b" in
  production) ;;
     staging) ;;
          qa) ;;
           *) echo "$0: Error: unknown argument -b \"${opt_b}\", exiting" >&2 ; exit 1 ;;
esac

# script does its work starting from here

Invoking with script-name -c -b qa will parse the options -b and -c as expected, and script-name -cb qa will also work.

I gave only one example of checking the options (to keep the answer simple); normally there would be more code to check each option and respond to legal and illegal combinations of options. I usually tuck those lines of code into a subroutine so they aren't in the middle of the script's main work flow.

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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