13

Trying to check for 3 conditions in one line of code, but I'm stuck.

Essentially, I need to code for the following:

IF

string1 is not equal to string2

AND

string3 is not equal to string4

OR

bool1 = true

THEN

display "conditions met - running code ...".

As requested in the comments, I've updated my example to try to make the problem clearer.

#!/bin/sh

string1="a"
string2="b"
string3="c"
string4="d"
bool1=true

# the easy-to-read way ....
if [ "$string1" != "$string2" ] && [ "$string3" != "$string4" ] ; then
    echo "conditions met - running code ..."
fi

if $bool1 ; then
    echo "conditions met - running code ..."
fi

# or the shorter way ...
[ "$string1" != "$string2" ] && [ "$string3" != "$string4" ] && echo "conditions met - running code ..."

$bool1 && echo "conditions met - running code ..."

The code above will potentially run twice: if the first 2 conditions are met, and then again if the 3rd condition is met. This is not what I need.

The issue with this example is that it involves 2 distinct calls to 'echo' - (note: in the real code, it's not an echo, but you get the idea). I'm trying to reduce the code duplication by combining the 3 condition check into a single command.

I'm sure there's a few people now shaking their heads and shouting at the screen "That's NOT how you do it!"

And there's probably others waiting to mark this as a duplicate ... well, I looked but I'm damned if I could figure out how to do this from the answers I've read.

Can someone please enlighten me ? :)

teracow
  • 352
  • 1
  • 4
  • 12
  • 2
    do you mean `A && (B || C)` or `(A && B) || C`? see http://stackoverflow.com/a/6270803/137158 – cas May 05 '16 at 07:55
  • in fact, there are several good answers/explanations (many from SE sites) with a simple google search: https://www.google.com.au/search?q=bash+boolean+logic – cas May 05 '16 at 08:02
  • I'm not sure what your problem is because it seems to do what you asked it to do. What's wrong according to you? – Julie Pelletier May 05 '16 at 08:05
  • Fix your example to either compare constant values or set the variables before the test. Then explain your results and expected results and how they differ – bsd May 05 '16 at 08:26
  • thanks for the quick answers you good people. :) I've updated my question to try to make it clearer. – teracow May 05 '16 at 09:14
  • Also, please note that this question is for 'sh' not 'bash'. :) – teracow May 05 '16 at 09:21
  • [stackoverflow simple logical operators in bash](http://stackoverflow.com/questions/6270440/simple-logical-operators-in-bash) – bsd May 05 '16 at 09:22
  • why tag it as /bash when it's a sh question, not a bash question? – cas May 05 '16 at 09:26
  • thanks cas and bsd for that link. I can probably adapt * if [ "$varA" = 1 ] && { [ "$varB" = "t1" ] || [ "$varC" = "t2" ]; }; then * and move the braces. – teracow May 05 '16 at 09:26
  • yes, sorry - used to typing bash - my bad. :) – teracow May 05 '16 at 09:28
  • Could you clarify that you want `if (A AND B) OR C`? I'm still not 100% sure that's what you need. Also, what system will this be running on? Do you really mean bourne shell (`sh`), or is that bash called as `sh` (which is the default on many systems) or `dash` called `sh` (which is the default on Ubuntu and perhaps others). – terdon May 05 '16 at 09:28
  • (sh) - inside my NAS. And yes "if (A AND B) OR C" is correct. – teracow May 05 '16 at 09:36
  • OK, if it's on your NAS, that's probably going to be busybox sh, yet another one. Please [edit] your question and make that clear. – terdon May 05 '16 at 09:37
  • lol... just been checking in the NAS.... found a symlink from bash to sh... then when you run 'sh -version', it says "GNU bash, version 3.2.57(3)-release (i686-pc-linux-gnu) Copyright (C) 2007 Free Software Foundation, Inc."... So bash it is? :) – teracow May 05 '16 at 09:41
  • Yes, that's bash running in POSIX compatibility mode. Which means you should be able to use `[[ "$string1" != "$string2" ] && "$string3" != $string4" ]] || "$bool1" && echo "continue"`. – terdon May 05 '16 at 09:54

2 Answers2

17

This will work:

if    [ "$string1" != "$string2" ] \
   && [ "$string3" != "$string4" ] \
   || [ "$bool1" = true ]; then
    echo "conditions met - running code ...";
fi;

Or surround with { ;} for readability and easy to maintain in future.

if { [ "$string1" != "$string2" ] && [ "$string3" != "$string4" ] ;} \
   || [ "$bool1" = true ] ; then
    echo "conditions met - running code ...";
fi;

Points to note:

  1. There is no such thing as a boolean variable..
  2. Braces need the final semicolon ({ somecmd;}).
  3. && and || evaluate left-to-right in the above — && has higher precedence than || only within (( )) and [[..]]

&& higher precedence only happen in [[ ]] is proven as follows. Assume bool1=true.

With [[ ]] :

bool1=true
if [[ "$bool1" == true || "$bool1" == true && "$bool1" != true ]]; then echo 7; fi #1 # Print 7, due to && higher precedence than ||
if [[ "$bool1" == true ]] || { [[ "$bool1" == true && "$bool1" != true ]] ;}; then echo 7; fi # Same as #1
if { [[ "$bool1" == true || "$bool1" == true ]] ;} && [[ "$bool1" != true ]] ; then echo 7; fi # NOT same as #1
if [[ "$bool1" != true && "$bool1" == true || "$bool1" == true ]]; then echo 7; fi # Same result as #1, proved that #1 not caused by right-to-left factor, or else will not print 7 here

With [ ] :

bool1=true
if [ "$bool1" == true ] || [ "$bool1" == true ] && [ "$bool1" != true ]; then echo 7; fi #1, no output, due to && IS NOT higher precedence than ||
if [ "$bool1" == true ] || { [ "$bool1" == true ] && [ "$bool1" != true ] ;}; then echo 7; fi # NOT same as #1
if { [ "$bool1" == true ] || [ "$bool1" == true ] ;} && [ "$bool1" != true ]; then echo 7; fi # Same as #1
if [ "$bool1" != true ] && [ "$bool1" == true ] || [ "$bool1" == true ]; then echo 7; fi # Proved that #1 not caused by || higher precedence than &&, or else will not print 7 here, instead #1 is only left-to-right evaluation
Stéphane Chazelas
  • 522,931
  • 91
  • 1,010
  • 1,501
林果皞
  • 4,946
  • 2
  • 29
  • 45
  • Thanks 林果皞, your second example looks like where I'm hoping to go with this. I'll test it and post back. :) – teracow May 05 '16 at 09:38
  • 1
    Actually, in this context, `&&` and `||` have the same precedence. Your first example works because it is executed with left associativity. The `&&` operator has higher precedence than `||` only in `(( ))` and `[[ ]]`. See [Precedence of the shell logical operators &&, ||](http://unix.stackexchange.com/q/88850). – terdon May 05 '16 at 09:52
  • @terdon you're right. – 林果皞 May 05 '16 at 09:56
  • 1
    @林果皞 - the second example works for me. Thanks. :) – teracow May 05 '16 at 20:03
  • 1
    There's something off with the last two sets of examples, at least `{ "$bool1" == true ]] || "$bool1" == true ;}` seems wrong, it's missing some `[[` and `]]`, but I'm not going to go through them right now. – ilkkachu Mar 02 '21 at 13:21
0

The && that you are using at the end of your conditional expression is meant for running a subsequent command (its second argument) if the preceding command (its first argument) returns a status of zero, which is traditionally considered a success code for programs. Also, the $bool1 that you have put there is standing alone, which makes Bash interpret its value as the name of a command or program to run. What you are trying to achieve seems to be a conditional expression evaluation, which is done using the [ command as follows:

if [ "$string1" != "$string2" ]  && [ "$string3" != "$string4" ] || [ $bool1 -ne 0 ]; then echo "conditions met - running code ..."; fi

You may see older code using the built-in "and", -a, and "or", -o, operators, but this has been deprecated by the POSIX specification and is not well-defined.

if [ "$string1" != "$string2" -a "$string3" != "$string4" -o $bool1 -ne 0 ]; then echo "conditions met - running code ..."; fi

If you are using a shell derived from ksh (such as bash) you can use the built-in test operator [[:

[[ "$string1" != "$string2" && "$string3" != "$string4" || $bool1 -ne 0 ]] && echo "conditions met - running code ..."

Note that the Boolean value is expressed as an integer, so the integer expression test for "not equal to", -ne, can be used here. The following is a script that demonstrates the working:

#!/bin/bash

string1="Hello"
string2="Hello"
string3="Hello"
string4="Hello"
bool1=1

if [ "$string1" != "$string2" ]  && [ "$string3" != "$string4" ] || [ $bool1 -ne 0 ]; then echo "conditions met - running code ..."; fi

exit 0
miken32
  • 446
  • 6
  • 16
  • Sorry - the title for my question should say 'sh' not 'bash' ... $bool1 "on it's own" is tested and if true, the subsequent command is then run. Not unusual to see and I've been using that method for years. $bool1 as in "boolean" i.e. true or false. Thanks. – teracow May 05 '16 at 09:21
  • 1
    @teracow note that if you set `foo=true` and then run `$foo || command`, that isn't checking that the variable is set to `true`, it runs the shell builtin command ["true"](http://pubs.opengroup.org/onlinepubs/9699919799/utilities/true.html) (same goes for `false`). So it isn't actually, strictly speaking, a boolean. – terdon May 05 '16 at 09:36
  • > "$bool1 on its own is tested and if true, the subsequent command is then run" `$bool1 && xyz` will interpret `$bool1` as a command or program, and, if the evaluation or return value is zero, it will then run `xyz`. This is not actually a test in the manner of the shell built-in `test` (`[]`) function. –  May 05 '16 at 09:44
  • @terdon - agreed - I name them according to how I think of them. :) However, I usually only use them for flags for debugging, etc.. as in $debug && echo "this variable is currently: $var"... I don't usually use || in that sense – teracow May 05 '16 at 09:46
  • Going by your statement of "testing" or evaluating `$bool1` and combining it with `&&`, you could also use the following: `[[ "$string1" != "$string2" && "$string3" != "$string4" || $bool1 == true ]] && echo "conditions met - running code ..."`. You can use parentheses to override precedence within the `[[...]]` block. –  May 05 '16 at 10:07
  • Thanks Saurav, your last one works for me as well. [[ "$string1" != "$string2" && "$string3" != "$string4" || $bool1 == true ]] && echo "conditions met - running code ..." – teracow May 05 '16 at 20:09