3

I have a variable named choice. I want to prompt for inputting a value for choice until it is non-empty and equal to either yes or no.

In other words: While choice is empty or different that yes and no then enter choice.

If I write it in Java it would be piece of cake with:

while (choice.IsEmpty() || (!choice.equals("yes") && !choice.equals("no"))

But I cannot find a way to translate it to shell without using the content (var between double quotes) syntax:

while [ "$choice" != "yes" && "$choice" != "no" ]

Obviously it works, but, for my personal knowledge, is there another way to test that the same way as in Java?

2
  • 1
    In bash, you can use double square brackets and omit the double quotes.
    – annahri
    Mar 2, 2021 at 11:16
  • I think you want "-a" option to test instead of "&&", which is a command separator
    – mpez0
    Mar 3, 2021 at 15:43

3 Answers 3

21

This

while [ "$choice" != "yes" && "$choice" != "no" ]

does actually not work, because the && breaks the [ command. Use either of:

while [  "$choice" != "yes" ] && [ "$choice" != "no" ]
while [[ "$choice" != "yes"   &&   "$choice" != "no" ]]

In Bash and other feature-rich shells, you could also use the regex match:

while [[ ! $choice =~ ^(yes|no)$ ]]

or the ksh-style extended glob, which may need to be explicitly enabled in at least Bash and Zsh:

# shopt -s extglob        # Bash, though not needed within [[ .. ]] 
                          #       in recent versions
# setopt kshglob          # Zsh
while [[ $choice != @(yes|no) ]]

Or in Zsh, with Zsh's own extended glob syntax:

while [[ $choice != (yes|no) ]]

(See e.g. this answer for the relations between the different extended globs.)

3
  • 1
    Note that [[...]] is a kshism (now also supported, though with many divergences by zsh, bash, mksh, yash, busybox ash/mush). Mar 2, 2021 at 13:20
  • 2
    Note that since 4.1 extglob is automatically enabled inside [[...]] in bash. Mar 2, 2021 at 13:31
  • 1
    Note that [[ $choice =~ ^(yes|no)$ ]] would return true for choice=$'yes\n' in zsh if the re_match_pcre option was enabled. With PCREs, you need \z instead of $ to match at the end of the subject (as opposed to either the end of the subject of just before a line delimiter at the end of the subject). So [[ ! $choice =~ '^(yes|no)\z' ]]. See also zsh.org/mla/workers/2017/msg01708.html Mar 3, 2021 at 18:44
13

A literal translation of While (choice.IsEmpty() || (!choice.equals("yes") && !choice.equals("no")) in POSIX sh syntax would look like:

while
  [ -z "$choice" ] || {
    ! [ "$choice" = yes ] &&
    ! [ "$choice" = no ]
  }
do
 ...
done

For a closer match, you could use ksh93 which has experimental support for object programming:

typeset -T java_string=(
  function IsEmpty
  {
    [[ -z $_ ]]
  }
  function equals
  {
    [[ $_ = "$1" ]]
  }
)

To declare a java_string type of object with two IsEmpty and equals methods and then:

java_string choice
while
  IFS= read -r 'choice?Enter your choice: ' || exit
  choice.IsEmpty || {
    ! choice.equals yes &&
    ! choice.equals no
  }
do
  print -ru2 "Wrong choice: $choice, try again."
done

But the [ -z "$choice" ] is redundant since if $choice is yes or no, obviously it's not empty.

until
  [ "$choice" = yes ] || [ "$choice" = no ]
do
  ...
done

would make more sense.

With the Korn shell (or zsh -o kshglob or bash -O extglob or bash4.1+), you can also do:

until
  [[ $choice = @(yes|no) ]]
do
  ...
done
12

is there another way to test that

You could use the standard case construct:

case $choice in yes|no) false ;; esac

which can be used even in the condition part of a while, though the construction may end up a bit confusing:

while case $choice in yes|no) false ;; esac; do 

the same way as in Java?

No

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