while loop does not exit when the condition is not met - bash

When running the code below, I’d like to read a user-input answer until it is one of the 6 possible answers ([abcABC]), else continue with the next loop.
However, it does not exit the while loop when I enter one of the accepted answers.
I tried [] and [[]] for the conditions, I tried to place all of the conditions into one pair of square brackets, I tried to use | and ||, none of them worked as expected.
while [ "$ans" != "a" ] || [ "$ans" != "A" ] || [ "$ans" != "b" ] || \
[ "$ans" != "B" ] || [ "$ans" != "c" ] || [ "$ans" != "C" ]; do
read ans
case $ans in
[aA]) echo "aA" ;;
[bB]) echo "bB" ;;
[cC]) echo "cC" ;;
*) echo "Try again." ;;
esac
done
It should read in loop until one of the accepted answer is given; then it should continue with the following code (if any).

Your loop condition is always true, and Bash correctly loops forever.
Consider a simplified version:
[ "$ans" != "a" ] || [ "$ans" != "b" ]
If ans is a then it becomes false || true which is true.
If ans is b then it becomes true || false which is true.
If ans is x then it becomes true || true which is true.
You wanted && instead of ||. Alternatively, do it in a single comparison: [[ "$ans" != [aAbBcC] ]]

Related

bash if clause with and or combined

I'm trying to be smart but it doesn't work. Can anybody help me to do this a bit simpler?
if [[ "${DATUM}" == "${today}" && "${STUNDE}" == "${HH24}" ]] || [[ "${DATUM}" == "${today}" && "${STUNDE}" == "20" ]] ||
[[ "${DATUM}" == "${today}" && "${STUNDE}" == "" && "20" == "${HH24}" ]]; then
Is there a way to combine it?
Your code can be translated to:
(C1 and C2) or (C1 and C3) or (C1 and C4 and C5)
Applying boolean arithmetics you can simplify it as:
C1 and (C2 or C3 or (C4 and C5))
This said, you can add a nested if statement to, first, check the C1 condition and, second, check the other conditions. It does not simplify the code a lot but here it is:
if [ "${DATUM}" = "${today}" ]; then
if [ "${STUNDE}" = "${HH24}" ] || [ "${STUNDE}" = "20" ] || { [ "${STUNDE}" = "" ] && [ "${HH24}" = "20" ]; }; then
# Insert the code to execute when the conditions are satisfied
fi
fi
As others have noted, your boolean expression can be simplified applying the law of distributivity of conjunction (⋀, *, AND) over disjunction (⋁, +, OR):
(a ⋀ b) ⋁ (a ⋀ c) = a ⋀ (b ⋁ c)
But to simplify it further, note you can, in bash, use && and || inside the (bash-specific) [[ .. ]] command:
[[ $a == 1 && $b == 2 ]]
Also, when using [[ .. ]] compound command (over POSIX [ .. ]) you don't have to quote variables. And to test for null-strings, you can use the shorter -z $var form over $var == "".
All this together yields:
if [[ $DATUM == $today ]] && [[ $STUNDE == $HH24 || $STUNDE == 20 || -z $STUNDE && $HH24 == 20 ]]; then
# ...
fi
To further simplify it, we would need to have more details on your application logic, possible values, etc.

checking if argument passed equals a string in bash script

Little explanation necessary: why the hell does this doesn't work?
#!/bin/bash
ker=$1
if [ "$ker" != "iso" ] || [ "$ker" != "om" ] || [ "$ker" != "constbeta" ] ; then
printf " allowed kernels: iso, om, constbeta \n"
exit
fi
wait
echo 'anisotropy kernel: ', "$ker"
I have also tried
#!/bin/bash
ker="$1"
if [ $ker != "iso" ] || [ $ker != "om" ] || [ $ker != "constbeta" ] ; then
printf " allowed kernels: iso, om, constbeta \n"
exit
fi
wait
echo 'anisotropy kernel: ', "$ker"
I call it like this: $ ./script.sh iso
and I've even tried like this (though I think this doesn't make sense with the
scripts above): $ ./script.sh "iso"
I always get allowed kernels: iso, om, constbeta
Many thanks to those who can spot the error.
because of the logical or ||, you should use and && otherwise the condition is always true thinking to the negation string can't be equals to the three value.
You've got an irrational condition...
if [ "$ker" != "iso" ] || [ "$ker" != "om" ] || [ "$ker" != "constbeta" ] ; then
If $ker is "iso", then it is not "om" and the condition matches. If $ker is "om" then it is not "iso" and the condition matches. What you want is to OR a list of positive checks and have an "else" condition, rather than OR the negative checks.
if [ "$ker" = "iso" ] || [ "$ker" = "om" ] || [ "$ker" = "constbeta" ] ; then
: do something useful
else
: report error
fi
Or, since you're in bash, you could use a "simpler" condition:
if [[ "$ker" =~ ^(iso|om|constbeta)$ ]]; then
Though if you like you could use other constructs:
case "$ker" in
iso|om|constbeta)
: this catches our "good" values
;;
*)
echo "Error" >&2
exit 1
;;
esac
This has the possible benefit of being POSIX compliant.

Shellscript missing ]

I have a shellscript that tells me missing ] in the line
if [ $status != "2" && $status != "3" && `echo "$temp1 > $upperLimit" | bc` = "1" ]
and also missing ] in the line
if [ $status = "2" && `cat motionsensordate` \> `date +%s` ]
Why is that?
The single [ doesn't support logical operators inside the brackets. You have to use them outside
if [ "$status" != 2 ] && [ "$status" != 3 ] ...
Use double quotes for variables in single brackets to prevent unary operator expected error when the variable is empty.
Or, switch to double brackets:
if [[ $status != 2 && $status != 3 ... ]]
Also, status different to 2 and 3 can be expressed by a pattern:
if [[ $status != [23] && ... ]]
And if you would like to (in addition to the answers here) group together conditions:
if [[ ( COND1 || COND2 ) && COND3 ]]
then
echo "$cmd"
break
fi

Comparing Strings in Bash with Logical Or

I'm having an issue getting a a simple y/n question to work. Consider the following code:
echo "Hi there"
read ans
if [[ $ans != "y" || $ans != "Y" || $ans != "YES" || $ans != "yes" ]]; then
echo "Foo"
exit 0
fi
I've looked at – I would argue – some of the more informative answers on StackOverflow for advice: Simple logical operators in Bash
I've tried all different types of variations such as:
if [[ ($ans != "y" || $ans != "Y" || $ans != "YES" || $ans != "yes") ]]; then
echo "Foo"
exit 0
fi
if [[ ($ans != "y*" || $ans != "Y*" || $ans != "YES*" || $ans != "yes*") ]]; then
echo "Foo"
exit 0
fi
if [[ ($ans != "y") || ($ans != "Y") || ($ans != "YES") || ($ans != "yes") ]]; then
echo "Foo"
exit 0
fi
Regardless of why I type in any of these cases, it automatically fails and I'm not sure why. If anyone has a better way to handle y/n answers then please let me know! Ideally I would like to use pattern matching (like I might do with Perl) but I'm not entirely sure the best way/most efficient way to accomplish a simple y/n question.
You need to use && instead of ||. As it stands you're saying if it's not equal to any of those possibilities, then execute the "then" block. You mean to say if it's not equal to all of them, then execute the "then" block. That requires &&.
You can use:
echo "Hi there"
read ans
case "$ans" in
y|Y|YES|yes)
;;
*)
echo "Foo"
exit 0
;;
esac
The logic needs to be adjusted:
echo "Hi there"
read ans
if ! [[ "$ans" == "y" || "$ans" == "Y" || "$ans" == "YES" || "$ans" == "yes" ]]; then
echo "Foo" # User answered no
exit 0
fi
The will echo "Foo" only if the answer was not one of "y", "Y" or "YES". By contrast, consider the original logic:
[[ $ans != "y" || $ans != "Y" || $ans != "YES" || $ans != "yes" ]]
At least one of these tests will be true regardless of what the user's answer is.
Using the case statement
You might consider using the case statement to analyze the user's answer:
read -p "Hi there: " ans
case "$ans" in
[yY]*) ;;
[nN]*) echo "Foo" ; exit 0 ;;
*) echo "You were supposed to answer yes or no" ;;
esac
Try read ans, not read $ans.

bash if logical boolean string

I am not looking for a different way to accomplish the apparent intention. I'm looking to understand why this exact syntax is not working.
[root#lvs ~]# while true;do
> echo "Would you like the script to check the second box ([y]n)?"
> read ans
> if [ "$ans" == "n" ];then
> echo
> echo "bye"
> exit
> elif [ "$ans" != "" -o "$ans" != "y" ];then
> echo "Invalid entry..."
> else
> break
> fi
> done
Would you like the script to check the second box ([y]n)? **"Should have continued"**
Invalid entry...
Would you like the script to check the second box ([y]n)? **"Should have continued"**
y
Invalid entry...
Would you like the script to check the second box ([y]n)? **"Correct behavior"**
alskjfasldasdjf
Invalid entry...
Would you like the script to check the second box ([y]n)? **"Correct behavior"**
n
bye
Here's a reference that's identical to so many others i found. I understand what it's doing, it's using the non logical's for AND and OR when everything I've read said that it should be using logical bools.
http://www.groupsrv.com/linux/about140851.html
Ok so here it is, with Nahuel's suggestion behaving how I had originally expected it to:
[root#lvs ~]# while true;do
> echo "Would you like the script to check the second box ([y]n)?"
> read ans
> if [ "$ans" = "n" ];then
> echo
> echo "bye!"
> exit
> elif [ "$ans" != "" -a "$ans" != "y" ];then
> echo "Invalid entry..."
> else
> break
> fi
> done
Would you like the script to check the second box ([y]n)?
asdfad
Invalid entry...
Would you like the script to check the second box ([y]n)?
[root#lvs ~]# while true;do
> echo "Would you like the script to check the second box ([y]n)?"
> read ans
> if [ "$ans" = "n" ];then
> echo
> echo "bye!"
> exit
> elif [ "$ans" != "" -a "$ans" != "y" ];then
> echo "Invalid entry..."
> else
> break
> fi
> done
Would you like the script to check the second box ([y]n)?
y
[root#lvs ~]# while true;do
> echo "Would you like the script to check the second box ([y]n)?"
> read ans
> if [ "$ans" = "n" ];then
> echo
> echo "bye!"
> exit
> elif [ "$ans" != "" -a "$ans" != "y" ];then
> echo "Invalid entry..."
> else
> break
> fi
> done
Would you like the script to check the second box ([y]n)?
n
logout
The problem is that : [ "$ans" != "" -o "$ans" != "y" ] is always true because of the or and the negation. $ans cannot be equal to "" and to "y".
Try replace these lines
if [ "$ans" == "n" ];then
elif [ "$ans" != "" -o "$ans" != "y" ];then
by these
if [ "$ans" = "n" ];then
elif [ "$ans" != "" -a "$ans" != "y" ];then
or these
if [[ $ans == n ]];then
elif [[ $ans != "" && $ans != y ]];then
The easier is to do is a case:
case $ans in
y) echo "yes"
;;
n) echo "no"
;;
*)
;;
esac
also break must be used only in a for or while loop, or in a select but it is missing in your post .
I don't really understand, why do you use -o in the elif. I would use "||" or "OR" operator. When you use two conditions in if, you should use double [[ and ]].
So if you use:
elif [[ "$ans" != "" || "$ans" != "y" ]];then
it works fine.
also logically its a flawed way of doing things.
firstly using case would be best in this scenario, secondly you are looking for == n then stating if it is blank or not equal to yes - so although no is caught out in first if statement in theory it would still meet second criteria
surely the most logical way to ensure input is 100% would be
if [ "$ans" == "n" ];then
echo
echo "bye"
exit
elif [ "$ans" == "y" ];then
echo Yes
break;
else
echo "Invalid entry... >$ans<"
fi

Resources