0

I'm new to bash, and the below while loop is driving me crazy. I want the loop to exit when the user enters y or n but it fails to do so. I have tried different ways but no luck. Anyone can point me in the right direction?

    echo "want to go for a walk Y/N "
    read answer
    while [ "$answer" != "y" ]  ||  [ "$answer" != "n" ]  ; do 
               
        echo "Enter y or n"
        read answer
    done

The second solution also is in the same scenario

    echo "want to go for a walk Y/N "
    read answer
    while [ "$answer" != "y"   ||   "$answer" != "n" ]  ; do 
               
        echo "Enter y or n"
        read answer
    done
2

2 Answers 2

2

Of course it won't exit. That loop never will.

In your code, answer is always NOT 'y' OR NOT 'n' (together, at the same time).

It is so because if you select 'y' then it's NOT 'n' and if you select 'n' then it's NOT 'y'.

If you have TRUE || FALSE = TRUE And if you have FALSE || TRUE = TRUE, so the loop keeps going.

The condition you want to use is && (AND), so while answer is NOT 'y' AND NOT 'n' then go on, but if it is one of them then exit.

TRUE && FALSE => FALSE and FALSE && TRUE => FALSE and that's what you need for the loop to finally end.

#!/bin/bash

echo "want to go for a walk y/n "
read -r answer
while [ "$answer" != "y" ]  &&  [ "$answer" != "n" ]  ; do 
           
    echo "Enter y or n"
    read -r answer
done

The -r flag in read -r var is to avoid reading backslash as an escape character. It is not relevant for this toy problem but it is a best practice to include it.

Comment from David: Keep in mind that the code doesn't handle case, so Y is not recognized as y.

Sign up to request clarification or add additional context in comments.

9 Comments

You will also never match 'Y' or 'N' checking for 'y' and 'n' :)
But of course! Good catch! I made it more explicit.
Other alternatives would be: while ! [ "$answer" = "y" ] || [ "$answer" = "n" ] or until [ "$answer" = "y" ] || [ "$answer" = "n" ]
You can convert the answer to lowercase inside your check with ${answer,,}
@WalterA it's worth noting that the syntax ${answer,,} is Bash 4.0 and can be subject to portability issues.
|
0

Don't forget in a while loop, the first commands list may also contain multiple statements:

#!/usr/bin/env sh

echo "want to go for a walk y/n "

while
  read -r answer
  case $answer in
    [yYnN]) false ;;
  esac
do
  echo "Enter y or n"
done

Or using until and Bash's Extended Regular Expression:

#!/usr/bin/env bash

echo "want to go for a walk y/n "

until
  read -r answer
  [[ $answer =~ [yYnN] ]]
do
  echo "Enter y or n"
done

And a featured, case-insensitive POSIX compatible implementation:

#!/usr/bin/env sh

echo "want to go for a walk y/n "

until
  read -r answer
  case $answer in
    [yY])
      echo "Handling Yes answer stuffs"
      ;;
    [nN])
      echo "Stuffs for No answer"
      ;;
    *)      # Landing here if invalid answer
      false # Proceed with the do...done statements
      ;;
  esac
do
  echo "Enter y or n"
done

1 Comment

great set of answers. I'd use while true; do ... break; done for (arguably) more robust alternative

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.