4

I am trying to create an if else bash script which makes use of 3 boolean variables:

  1. CHECK_X
  2. CHECK_Y
  3. CHECK_Z

If either CHECK_X or CHECK_Y is false and CHECK_Z is also, false then I inform the user the conditions are not met. So I created the following:

if [[ "$CHECKX" || "$CHECKY" == "FALSE" ]] && [[ "$CHECKZ" == "FALSE" ]]; then
  echo "Condition not met"
fi

Using the code above, if either CHECK_X or CHECK_Y are correct despite CHECK_Z is still false the condition continues, which is not what I want.

I need either CHECK_X and CHECK_Z to be true or CHECK_Y and CHECK_Z to be true before my condition is met.

Using the following code also didn't work:

if [[ "$CHECK_X" && "$CHECK_Z" == "FALSE" ]] || [[ "$CHECK_Y" && "$DCHECK_Z" == "FALSE" ]]; then
  echo "Condition not met"
fi    

I think I have overcomplicated myself somewhere and was hoping for some expert advice. Thanks

UPDATE:

From some recent feedback I updated my code to:

if [[ "$CHECK_X" == "FALSE" || "$CHECK_Y" == "FALSE" ]] && [[ "$CHECK_Z" == "FALSE" ]]; then
  echo "Condition not met"
fi

However upon testing I have the following...

if:

  1. CHECK_X="TRUE"
  2. CHECK_Y="FALSE"
  3. CHECK_Z="TRUE"

Output: Condition not met. This is incorrect because conditions 1 and 3 are TRUE.

if:

  1. CHECK_X="TRUE"
  2. CHECK_Y="FALSE"
  3. CHECK_Z="FALSE"

Output: Condition not met. This is correct because conditions 1 and 3 are both not TRUE.

if:

  1. CHECK_X="FALSE"
  2. CHECK_Y="TRUE"
  3. CHECK_Z="FALSE"

Output: The script completes. This is incorrect because conditions 2 and 3 are not TRUE.

if:

  1. CHECK_X="FALSE"
  2. CHECK_Y="TRUE"
  3. CHECK_Z="TRUE"

Output: The script completes. This is correct because conditions 2 and 3 are TRUE.

if:

  1. CHECK_X="FALSE"
  2. CHECK_Y="FALSE"
  3. CHECK_Z="FALSE"

Output: Condition not met. This is correct because conditions 1 and 3 or 2 and 3 are not TRUE.

6
  • 1
    You are leaving out important information required to help you troubleshoot your issue. Mainly, how you are assigning the variables. Your if condition as written passes the five tests in the question as far as I can tell, I can only assume you and I are assigning the variables differently. Try using set -x at the top of your bash script to get some troubleshooting information. Dec 6, 2023 at 19:33
  • 2
    Rather than thinking in negatives, consider testing for when the condition is met. Also, consider constructing a truth table for your X Y and Z variables, showing what the output condition is for all of the eight possible input cases. That may help you spot a simpler Boolean solution.
    – Jim L.
    Dec 6, 2023 at 19:42
  • 1
    with the code after "UPDATE" and the first set of values under, setting CHECK_X="TRUE" CHECK_Y="FALSE" CHECK_Z="TRUE" and running if [[ "$CHECK_X" == "FALSE" || "$CHECK_Y" == "FALSE" ]] && [[ "$CHECK_Z" == "FALSE" ]]; then echo "Condition not met"; fi does not go to the main branch of the if, and does not print anything. Unlike the claim here seems to be.
    – ilkkachu
    Dec 7, 2023 at 9:22
  • 1
    Write a complete script that shows everything that is done, including setting the values and running the condition. Print the values you have with e.g. printf "<%s>\n" "$CHECK_X" e.g. to see that there is no stray whitespace or such messing you up.
    – ilkkachu
    Dec 7, 2023 at 9:23
  • 1
    @hymcode, yes, and I just copypasted the assignments and the conditional from the post above, and I do not get the result you mention here. (And indeed as you say the result mentioned would be incorrect.) Either the condition is different from what you showed here, or you've tested with values other than the ones you show. The point in posting a complete script is that one can copy it in full to test it, very much mitigating the possibility of accidentally running a test with some other code, which can easily happen when picking multiple parts of code from here and there.
    – ilkkachu
    Dec 7, 2023 at 9:29

7 Answers 7

8

You need to check each variable:

if [[ "$CHECKX" == "FALSE" || "$CHECKY" == "FALSE" ]] && [[ "$CHECKZ" == "FALSE" ]]; then
  echo "Condition not met"
fi

When you had if [[ "$CHECKX" || "$CHECKY" == "FALSE" ]], that is just checking if $CHECKX is set, it only compares $CHECKY to the string FALSE. To illustrate:

$ CHECKX=foo
$ CHECKY=TRUE
$ [[ "$CHECKX" || "$CHECKY" == "FALSE" ]] && echo true
true
$ CHECKX=""
$ [[ "$CHECKX" || "$CHECKY" == "FALSE" ]] && echo true
$ 

As you can see above, the check only failed when CHECKX was set to the emtpy string so the test [[ "$CHECKX" ]] evaluates to false. If it set to anything at all, including 0, then it fails:

$ CHECKX=0
$ [[ "$CHECKX" || "$CHECKY" == "FALSE" ]] && echo true
true
3
  • Thank you for your input. I have tried your suggestion, but for some reason when CHECK_X and CHECK_Z are correct, my script states the condition is not met, but this is a valid condition.
    – hymcode
    Dec 6, 2023 at 17:25
  • 1
    @hymcode please edit your question and add the code you use for that. Remember that FALSE isn't special in the shell, it is just a string. So unless you have explicitly set the variable to that value it won't work. How are you checking if the variable is true?
    – terdon
    Dec 6, 2023 at 18:00
  • I have updated my question accordingly now so hopefully it makes more sense. In my complete code I am checking the values assigned to my variables to ensure that the required TRUE or FALSE value has been explicitly set.
    – hymcode
    Dec 6, 2023 at 18:28
7

Others have addressed your coding techniques for storing and testing Boolean values and variables, so I will focus just on the logic of your code.

When you're working on getting your head around a multi-variable Boolean function, build a truth table so that you are clear on what the correct output of your function should be. Please check that I have understood your post correctly, but I believe that for a function Q(X,Y,Z) the value of Q depends on X, Y and Z as follows:

"If either X or Y is false and Z is also false then ... the conditions are not met."

Because you've stated this in the negative, I will assume that in all other cases, the condition is met.

X Y Z Q
FALSE FALSE FALSE FALSE
FALSE FALSE TRUE TRUE
FALSE TRUE FALSE FALSE
FALSE TRUE TRUE TRUE
TRUE FALSE FALSE FALSE
TRUE FALSE TRUE TRUE
TRUE TRUE FALSE TRUE
TRUE TRUE TRUE TRUE

A common axiom in boolean algebra is that instead of building one function Q that solves all the cases, we can build two sub-functions Q1 and Q2 that solve exclusive parts of the table. Then we can simply OR the results of Q1 and Q2 to get the final solution Q.

For example, let Q1 be the solution where X is FALSE, in the top half of the table. Notice that in those four cases, the Z and Q columns are identical. The Y column is irrelevant, so we can say that when X is FALSE, the function result is directly related to Z:

for X=FALSE, Q1 = !X && Z
X Y Z Q1
FALSE FALSE FALSE FALSE
FALSE FALSE TRUE TRUE
FALSE TRUE FALSE FALSE
FALSE TRUE TRUE TRUE

In the bottom half of the table, where X is TRUE, the function result Q2 is directly related to Y || Z:

for X=TRUE, Q2 = X && (Y || Z)
X Y Z Q2
TRUE FALSE FALSE FALSE
TRUE FALSE TRUE TRUE
TRUE TRUE FALSE TRUE
TRUE TRUE TRUE TRUE

Since Q1 and Q2 are defined over entirely separate domains, we can state that the result Q will be precisely one or the other. That is,

Q = Q1 || Q2
X Y Z Q1 Q2 Q1 || Q2
FALSE FALSE FALSE FALSE FALSE
FALSE FALSE TRUE TRUE TRUE
FALSE TRUE FALSE FALSE FALSE
FALSE TRUE TRUE TRUE TRUE
TRUE FALSE FALSE FALSE FALSE
TRUE FALSE TRUE TRUE TRUE
TRUE TRUE FALSE TRUE TRUE
TRUE TRUE TRUE TRUE TRUE

Now we need to combine these two cases into a single Boolean formula. It's tempting to simply concatenate everything, but for a correct Boolean formula, we have to consider the order of evaluation. Similar to normal algebra, where multiplication is performed before addition, Boolean algebra dictates that intersection (AND) is performed before union (OR). So we will put parentheses around the sub-expressions.

Either X is false and our result is Z:

if !X then Q1 = Z

Or X is true and our result is Y || Z:

if  X then Q2 = Y || Z

And our answer is literally one OR the other:

Q = (!X && Q1) || (X && Q2)

Short-circuit evaluation and commutation of union would allow us to re-state that as:

Q = (X && Q2) || (Q1)

Substituting for Q1 and Q2:

Q = (X && (Y || Z)) || (Z)

With parentheses only where needed:

Q = X && (Y || Z) || Z

which can also be written:

Q = Z || X && (Y || Z) 

When Z is true, the right side of the OR is irrelevant. When Z is false, the right side of the OR becomes:

X && (Y || FALSE)

or just:

X && Y

so our final function Q is:

Q = Z || X && Y

We can verify this against the truth table, because in every case where Z is true, Q is also true. In the four cases where Z is false, Q is true only when X and Y are both true.

X Y Z X && Y Q
FALSE FALSE FALSE FALSE FALSE
FALSE FALSE TRUE FALSE TRUE
FALSE TRUE FALSE FALSE FALSE
FALSE TRUE TRUE FALSE TRUE
TRUE FALSE FALSE FALSE FALSE
TRUE FALSE TRUE FALSE TRUE
TRUE TRUE FALSE TRUE TRUE
TRUE TRUE TRUE TRUE TRUE
1
  • 1
    Thank you very much for your detailed response. You are absolutely right about creating a truth table. I'd honestly completely forgot about this technique. Greatly appreciated.
    – hymcode
    Dec 8, 2023 at 9:07
6

Expressing your requirements in [multiple] double negatives ("If either CHECK_X or CHECK_Y is false and CHECK_Z is also, false then I inform the user the conditions are not met.") makes it much harder to understand and implement them than if you expressed them in a positive way.

Avoid using negatives like ! or not or comparisons with any variation of false in your requirements or code and avoid using any negative term like not in your variable names for the same reason - positives are always easier to understand than negatives and if you don't have negatives then you can't end up with the extremely hard to understand double negatives.

Here's what you said your requirement is:

If either CHECK_X or CHECK_Y is false and CHECK_Z is also, false then I inform the user the conditions are not met.

and we can directly translate your requirements to pseudo-code as:

if ( ((CHECK_X == false) || (CHECK_Y == false)) && (CHECK_Z == false) ); then
    print 'conditions are not met'
else
    conditions are met so do nothing
fi

Now, to make it all less negative, if we first flip the if to have "conditions are met" (i.e. positive rather than negative) be the condition being tested then it becomes:

if ! ( ((CHECK_X == false) || (CHECK_Y == false)) && (CHECK_Z == false) ); then
    conditions are met so do nothing
else
    print 'conditions are not met'
fi

So now we have an if test that gets us to a positive "conditions are met" rather than negative "conditions are not met" state. Now we just need to make the if test itself positive rather than negative by factoring out the leading "not" operator, !, and changing comparisons with false to comparisons with true.

If we apply boolean algebra to move the negation ! inside the outermost parentheses then the condition becomes:

if ( !((CHECK_X == false) || (CHECK_Y == false)) || !(CHECK_Z == false) ); then
    conditions are met so do nothing
else
    print 'conditions are not met'
fi

and if we apply the same again to move the first ! inside the condition it applies to we get:

if ( ( !(CHECK_X == false) && !(CHECK_Y == false) ) || !(CHECK_Z == false) ); then
    conditions are met so do nothing
else
    print 'conditions are not met'
fi

which we can simplify by changing each !...false to true:

if ( ((CHECK_X == true) && (CHECK_Y == true)) || (CHECK_Z == true) ); then
    conditions are met so do nothing
else
    print 'conditions are not met'
fi

and now we have an entirely positive if test producing a positive result "conditions are met" and so suddenly it becomes very clear that your requirement is simply that if CHECK_Z or both of CHECK_X and CHECK_Y are true then the condition is met, otherwise it isn't and so we can easily implement that requirement using an if-else as above or more succinctly as:

$ cat tst.sh
#!/usr/bin/env bash

testit() {
    [[ (("$CHECK_X" == "TRUE") && ("$CHECK_Y" == "TRUE")) || ("$CHECK_Z" == "TRUE") ]] ||
        printf 'conditions are not met\n'
}

for CHECK_X in TRUE FALSE; do
    for CHECK_Y in TRUE FALSE; do
        for CHECK_Z in TRUE FALSE; do
            printf 'CHECK_X=%s, CHECK_Y=%s, CHECK_Z=%s %s\n' \
                    "$CHECK_X" "$CHECK_Y" "$CHECK_Z" "$(testit)"
        done
    done
done

$ ./tst.sh
CHECK_X=TRUE, CHECK_Y=TRUE, CHECK_Z=TRUE
CHECK_X=TRUE, CHECK_Y=TRUE, CHECK_Z=FALSE
CHECK_X=TRUE, CHECK_Y=FALSE, CHECK_Z=TRUE
CHECK_X=TRUE, CHECK_Y=FALSE, CHECK_Z=FALSE conditions are not met
CHECK_X=FALSE, CHECK_Y=TRUE, CHECK_Z=TRUE
CHECK_X=FALSE, CHECK_Y=TRUE, CHECK_Z=FALSE conditions are not met
CHECK_X=FALSE, CHECK_Y=FALSE, CHECK_Z=TRUE
CHECK_X=FALSE, CHECK_Y=FALSE, CHECK_Z=FALSE conditions are not met
1
  • 1
    Thank you very much for your response, reading this has been very insightful and even helped make me understand how better to improve my code.
    – hymcode
    Dec 8, 2023 at 9:09
3

I would focus on the readability of the logic flow in the code, so instead of writing a single complicated structure that does all the work, that later when you forgot about and need to understand you have to go through mental gymnastics, make the code into simple steps that are easy to follow.

For example, if I understand you correctly, if CHECK_Z is TRUE then you don't need to check on CHECK_X or CHECK_Y. So only if CHECK_Z is FALSE you have to check the others. I added a variable conditions_met, that you will use when you need to follow the program and print messages, for example.

I simplified the variable names for clarity, and assumed each one can have a boolean value, either 'TRUE' or 'FALSE' (in bash I'd use 0 and 1 maybe, not a string).

X="FALSE"
# X="TRUE"
Y="FALSE"
# Y="TRUE"
Z="FALSE"
# Z="TRUE"

# Set a default value for the conditions met that you will challenge
conditions_met="TRUE"
message="Conditions are met."

# The simpler case, Z value, only deal with the others if it's FALSE
if [[ "$Z" = "FALSE" ]]; then
    # If and only if Z is FALSE, check the value of the others
  if [[ "$X" = "FALSE" || "$Y" = "FALSE" ]]; then
    # Let the result of the operation be handled by another variable
    # that you can understand and follow easily
    conditions_met="FALSE"
  fi
fi

if [[ "$conditions_met" = "FALSE" ]]; then
  message="Conditions are NOT met."
fi

echo "$message"
2
  • I absolutely agree. Since asking my question and all the responses provided, I have realized the lack of readability even my code has also caused me a lot of confusion in what I am trying to achieve.
    – hymcode
    Dec 8, 2023 at 9:17
  • 1
    @hymcode Glad my answer provided some value to you, and thank you for your engagement with all the people here. It's been a while since I posted an answer/question here :). If any follow up an this answer, don't hesitate to comment. Dec 8, 2023 at 13:36
0

If you want Booleans in BASH, than the closest to a real Boolean is using a
Number and the Shell Arithmetic.

Because: CHECK_X="true"; [[ $CHECK_X == "true" ]];
is a string comparison, where the right parameter is a glob pattern, if not quoted, and which then generates something like a boolean, exit code 0 or 1

try shell arithmetic:

set +o histexpand # only needed in an interactive shell

declare -i CHECK_X=0 CHECK_Y=0 CHECK_Z=0   #-- Boolean 0 or 1 , false or true

# now you can use boolean syntax inside shell arithmetic ((...))
if (( (!CHECK_X || !CHECK_Y) && !CHECK_Z )); then echo "yep"; fi
if (( CHECK_X || CHECK_Y || !CHECK_Z )); then echo "pep"; fi

declare test="hello"  # with string
if (( CHECK_X && CHECK_Y && CHECK_Z && test=="hello" )); then echo ".."; fi
-1

Honestly, with all the information you have posted it's quite hard to pick out how you want this to work.

I'm basing my solution below on your statement as follows:

I need either CHECK_X and CHECK_Z to be true or CHECK_Y and CHECK_Z to be true before my condition is met.

As otherwise discussed, chaining negative conditions is hard work. For this sort of logic, you might be better off misusing a case inside a function -- and checking for the positives instead.

checks(){
  case "$CHECK_X:$CHECK_Y:$CHECK_Z" in
    (TRUE:*:TRUE) true ;; # X and Z
    (*:TRUE:TRUE) true ;; # Y and Z
    (*) false
  esac
}

if checks ; then
  echo conditions met
else
  echo conditions not met
fi

Here it is in action

X=TRUE  Y=TRUE  Z=TRUE  conditions met
X=TRUE  Y=TRUE  Z=FALSE conditions not met
X=TRUE  Y=FALSE Z=TRUE  conditions met
X=TRUE  Y=FALSE Z=FALSE conditions not met
X=FALSE Y=TRUE  Z=TRUE  conditions met
X=FALSE Y=TRUE  Z=FALSE conditions not met
X=FALSE Y=FALSE Z=TRUE  conditions not met
X=FALSE Y=FALSE Z=FALSE conditions not met

Try it online!

3
  • In principle, at least as I understand OP has presented the requirements, if Z is TRUE conditions are met, regardless of the value of X and Y. Check the tables of this answer. Dec 9, 2023 at 0:44
  • 1
    But that directly contradicts the quote I have used. Hence my remark that as posed the question is unclear. However the OP could readily adapt my method to suit any requirement with relative ease.
    – bxm
    Dec 9, 2023 at 9:24
  • You are right, sorry, OP's requirements seem contradictory, I was following the first statement "If either CHECK_X or CHECK_Y is false and CHECK_Z is also, false", and saw other answers (like the tables one) do that, so I assumed that was some kind of consensus. Dec 9, 2023 at 13:46
-1

my take on it:

if [[ ( $CHECK_X == 'TRUE' && $CHECK_Y == 'TRUE' ) || ( $CHECK_Y == 'TRUE' && $CHECK_Z == 'TRUE' ) ]]
  then echo Condition is met
  else echo condition is not met
fi
3
  • 2
    Beside other problems that your code could have, you have mispelled variable names, $CHECKX and CHECKY instead of $CHECK_X and CHECK_Y. Dec 9, 2023 at 0:38
  • Well, you are right about variable names. Now corrected it. Another example why it is not the best idea to code when you're feel sick and are on hurry. Still, logically-wise my code is 100% correct, I did retest it right now. Which "other problems" you mean? If you are reffering to it being not idiomatic, I did it on a purpose. Dec 20, 2023 at 10:50
  • I didn't mean that your code has problems, and given that OP's requirements are not that clear, I wouldn't be sure if your answer meets them. FWIW I didn't downvote (I almost never do). Dec 20, 2023 at 15:13

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .