Comparing multiple options in a bash (string)

11,063

Solution 1

Why don't you use case?

case $text in 
  us-east-1|us-west-2|us-west-1|eu-central-1|ap-southeast-1|etc) 
         echo "Working"
  ;;

  *)
         echo "Invalid option: $text"
  ;;
esac 

Solution 2

Why not make the life of the user a bit easier by not requiring them to type the name of the region at all?

#!/bin/bash

echo "Select region"

PS3="region (1-10): "

select region in "us-east-1" "us-west-2" "us-west-1" "eu-central-1" \
    "ap-southeast-1" "ap-northeast-1" "ap-southeast-2" \
    "ap-northeast-2" "ap-south-1" "sa-east-1"
do
    if [[ -z $region ]]; then
        echo "Invalid choice: '$REPLY'" >&2
    else
        break
    fi
done

echo "You have chosen the '$region' region"

If the user types in anything other than a valid numerical option from the list, the value in $region will be an empty string and we display an error message. If the choice is valid, the loop exits.

Running it:

$ bash script.sh
Select region
1) us-east-1         4) eu-central-1     7) ap-southeast-2  10) sa-east-1
2) us-west-2         5) ap-southeast-1   8) ap-northeast-2
3) us-west-1         6) ap-northeast-1   9) ap-south-1
region (1-10): aoeu
Invalid choice: 'aoeu'
region (1-10): .
Invalid choice: '.'
region (1-10): -1
Invalid choice: '-1'
region (1-10): 0
Invalid choice: '0'
region (1-10): '
Invalid choice: '''
region (1-10): 5
You have chosen the 'ap-southeast-1' region

Solution 3

The problem is with this:

[ "$text" != 'us-east-1' -o us-west-2 -o ... ]

The -o means or and you need a full condition, so it would be

[ "$text" != 'us-east-1' -o "$text" != 'us-west-2' -o ... ]

See how we're having to test $text each time?

Your logic is also wrong; you want -a (and); if it's not "us-east-1" and it's not "us-west-2" and it's not...

So

[ "$text" != 'us-east-1' -a "$text" != 'us-west-2' -a ... ]

There are other ways of doing this sort of test; some of which are merely "personal preference". However this syntax should get you going and follows the form and structure of your original.

Solution 4

I really can't stand scripts that ask me questions rather than allow me to use command line options (easier to edit and re-use previous command lines, easier to use in a script), so I'd use getopts like this:

#!/bin/bash

regions=(us-east-1 us-west-2 us-west-1 eu-central-1 ap-southeast-1
         ap-northeast-1 ap-southeast-2 ap-northeast-2 ap-south-1
         sa-east-1)

region=0  # default to region 0, us-east-1

usage() {
  [ -n "$1" ] && printf '%s\n\n' "$@"

  echo "Usage: $0 [ -r region-number|\"list\"] ..."
  # print more help here
  exit 1
}

list_regions() {
  [ -n "$1" ] && printf '%s\n\n' "$@"

  printf '%s\n' "${regions[@]}" | cat -n
  exit 1
}

check_region() {
  [ "$region" == "list" ] && list_regions
  [[ ! "$region" =~ ^[0-9]+$ ]] && usage "Region code must be numeric"

  region=$((region - 1))  # bash arrays are zero-based
  [ -z "${regions[$region]}" ] || \
    [ "$region" -lt 0 ] && list_regions "Unknown region code"
}

while getopts 'r:h' opt ; do
  case "$opt" in
     r) region="$OPTARG" ; check_region ;;

     h) usage ;;
     *) usage ;;
    esac
done
shift $(($OPTIND - 1))

# do whatever with "$region" and/or "${regions[$region]}"
echo region="${regions[$region]}"

Some example runs:

$ ./busted.sh 
region=us-east-1
$ ./busted.sh -r
./busted.sh: option requires an argument -- r
Usage: ./busted.sh [-r region-number|"list"] ...
$ ./busted.sh -r list
     1  us-east-1
     2  us-west-2
     3  us-west-1
     4  eu-central-1
     5  ap-southeast-1
     6  ap-northeast-1
     7  ap-southeast-2
     8  ap-northeast-2
     9  ap-south-1
    10  sa-east-1
$ ./busted.sh -r 99
Unknown region code

     1  us-east-1
     2  us-west-2
     3  us-west-1
     4  eu-central-1
     5  ap-southeast-1
     6  ap-northeast-1
     7  ap-southeast-2
     8  ap-northeast-2
     9  ap-south-1
    10  sa-east-1
$ ./busted.sh -r 7
region=ap-southeast-2

Solution 5

You could do something like this:

valid=(foo bar doo)
echo enter something, valid values: "${valid[@]}"
read text
ok=0
for x in "${valid[@]}" ; do 
    if [ "$text" = "$x" ] ; then ok=1 ; fi ; 
done 
echo is it ok: $ok

The valid values are saved in a bash array, which can be used both for display and to test the input string.

Apart from the fact that -o in test need a full condition, there are also arguments that one shouldn't use

[ "$x" != "foo" -a "$x" != "bar" ] 

but instead

[ "$x" != "foo" ] && [ "$x" != "bar" ] 
Share:
11,063

Related videos on Youtube

Busted
Author by

Busted

Updated on September 18, 2022

Comments

  • Busted
    Busted over 1 year

    I'm trying to enable only certain options when using the read command, and to exit the script if a wrong possibility was entered.

    Tried many possibilities (array, variables, syntax change), but I'm still stuck with my initial problem.

    How do I test the input of the user and allow \ disallow to run the rest of the script?

    #!/bin/bash
    
    red=$(tput setaf 1)
    textreset=$(tput sgr0) 
    
    echo -n 'Please enter requested region > '
    echo 'us-east-1, us-west-2, us-west-1, eu-central-1, ap-southeast-1, ap-northeast-1, ap-southeast-2, ap-northeast-2, ap-south-1, sa-east-1'
    read text
    
    if [ -n $text ] && [ "$text" != 'us-east-1' -o us-west-2 -o us-west-1 -o eu-central-1 -o ap-southeast-1 -o ap-northeast-1 -o ap-southeast-2 -o  ap-northeast-2 -o ap-south-1 -o sa-east-1 ] ; then 
    
    echo 'Please enter the region name in its correct form, as describe above'
    
    else
    
    echo "you have chosen ${red} $text ${textreset} region."
    AWS_REGION=$text
    
    echo $AWS_REGION
    
    fi
    
  • Busted
    Busted almost 8 years
    Awesome answer ! I'll be sure to check this out. In my case , since it is a part of a bigger script with many implication- (money for starters), I don't want to hear later on- oopsy.. did I set up the whole environment in another region? - didn't mean too..
  • Stéphane Chazelas
    Stéphane Chazelas almost 8 years
    A simpler way to check for a valid entry is to check $region against the empty string. See also $PS3 to change the prompt. Better to issue the error on stderr.
  • Kusalananda
    Kusalananda almost 8 years
    @StéphaneChazelas Much appreciated! Will edit.
  • Kusalananda
    Kusalananda almost 8 years
    @StéphaneChazelas Sorry to ask a question here, but do you have any clues as to why the PS3 prompt is actually displayed on stderr?
  • Stéphane Chazelas
    Stéphane Chazelas almost 8 years
    @Kusalananda, same for the choice or for bash's prompt or read -p. It's not standard output of your script, not something that you would pipe to something else. It's only for user interaction. If you redirect the script's output, you'll still want to see those prompts.
  • Busted
    Busted almost 8 years
    Great answer! simple and easy, I've even added an error message in case anyone just press 'enter' (which display $text as an empty field- so although it is the correct behavior, it seems like an error)(stackoverflow.com/questions/17575392/…)
  • Busted
    Busted almost 8 years
    Thank you for explaining it, It seems I did messed up the logic- oddly enough I did try it like you said, I guess I was too tired..
  • Luciano Andress Martini
    Luciano Andress Martini almost 8 years
    You can add this clause "") echo "Please enter some data" ;;. Note that it needs to come first than *) clause.
  • Luciano Andress Martini
    Luciano Andress Martini almost 8 years
    Hellow friend, very nice script! +1
  • Alessio
    Alessio almost 8 years
    thanks. on looking at this again, it's a little too verbose for my liking so I'd probably make the "Unknown region code" error message just say use '-r list' for a list rather than printing the entire list. and use column or something in the list_regions function so that the list didn't take up so many output lines.