How to use bitwise operators in if statements?

2019-03-18 09:38发布

问题:

I want to write something like this:

if [[ ( releases["token"] & $MASK ) -eq 1 ]]; then

but I get the error that:

unexpected token `&', conditional binary operator expected

How do I use bitwise operators in if statements?

回答1:

You can use Arithmetic Expansion:

(((5&3)==1)) && echo YES || echo NO

It will print YES



回答2:

Logic vs Syntax

.... or: "What's more to say?"

At first sight, as pointed out in @chepner's comment, the question might only be one of syntax, which causes the compilation error (unexpected token '&', conditional binary operator expected). And in fact, @kev's answer addresses that by using arithmetic expansion, which is applied to the If statement in @Gustavo's answer.

However, the question "how to use bitwise operators in if statements" is formulated in a more general manner and begs for an answer on how to use bitwise operators to check against $MASK (It was not the question of "how to avoid a syntax error when using binary comparison").

In fact, it may be assumed that the example code

if [[ ( releases["token"] & $MASK ) -eq 1 ]]; then

was a work in progress of finding a solution and is explicitly marked as "something like ...". Now @kev's arbitrary assumption of

 releases["token"]=3
 MASK=5

might hide the fact, that there is also a logical misunderstanding in using -eq 1 in the first place. So while @kev's answer works with the chosen values, the result of the input of e.g. 4 line in

 (((5&4)==1)) && echo YES || echo NO

would print NO, even though one would expect YES in that case as well, as 4 and 5 both have the 3rd bit set.

So this answer addresses this logical error in the questions example and attempts an general answer the the question's headline.


First things first!

... or: "The Background of Bitwise Representation"

to understand bitwise comparison it is helpful to visualise numbers in their bit representation (listed top-down in the following example):

|-----------------------------------------------------------|
|         |     hexadecimal representation of the value     |
| bit val |  0  1  2  3  4  5  6  7  8  9  A  B  C  D  E  F |
|---------|---------------------- bit ----------------------|
|         |  shows if the given value has the given bit set |
|  0      |  -  x  -  x  -  x  -  x  -  x  -  x  -  x  -  x |
|  1      |  -  -  x  x  -  -  x  x  -  -  x  x  -  -  x  x |
|  2      |  -  -  -  -  x  x  x  x  -  -  -  -  x  x  x  x |
|  3      |  -  -  -  -  -  -  -  -  x  x  x  x  x  x  x  x |
|---------|---------------------- val ----------------------|
|     shows the value that the given bit adds to the number |
|  0   1  |  0  1  0  1  0  1  0  1  0  1  0  1  0  1  0  1 |
|  1   2  |  0  0  2  2  0  0  2  2  0  0  2  2  0  0  2  2 |
|  2   4  |  0  0  0  0  4  4  4  4  0  0  0  0  4  4  4  4 |
|  3   8  |  0  0  0  0  0  0  0  0  8  8  8  8  8  8  8  8 |
|---------|-------------------------------------------------|
|sum:  15 |  0  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 |
|         |       decimal representation of the value       |
|===========================================================|

which is created by the following snippet:

( # start a subshell to encapsulate vars for easy copy-paste into the terminal
  # do not use this in a script!
echo ;
echo "|-----------------------------------------------------------|";
echo "|         |     hexadecimal representation of the value     |";
echo "| bit val |  0  1  2  3  4  5  6  7  8  9  A  B  C  D  E  F |";
echo "|---------|---------------------- bit ----------------------|";
echo "|         |  shows if the given value has the given bit set |";
mask=0;
for (( bit=0; bit<4; bit++)); do
    mask=$((1<<bit));
    echo -n "|  $bit      |"
    for ((x=0;x<16;x++)); do ((((x&mask)>0))&&echo -n '  x'||echo -n '  -'); done
    echo " |";
done ;
echo "|---------|---------------------- val ----------------------|";
echo "|     shows the value that the given bit adds to the number |";
mask=0;
for (( bit=0; bit<4; bit++)); do
    mask=$((1<<bit));
    echo -n "|  $bit   $mask  |"
    for ((x=0;x<16;x++)); do echo -n "  $((x&mask))"; done
    echo " |";
done ;
echo "|---------|-------------------------------------------------|";
echo "|sum:  15 |  0  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 |";
echo "|         |       decimal representation of the value       |";
echo "|===========================================================|";
echo ;
)


The curious case of 3 & 5 vs 4 & 5

... or: "==1 vs >0 and the Differences of &, |, and ^"

if we now check the special case of the examples mentioned, we'll see the shortcoming:

|------------------ value: 3 / mask: 5 -------------------|
|         | value | mask  |  and  |  or   |  xor  | xnor  |
|         |   3   |   5   | 3 & 5 | 3 | 5 | 3 ^ 5 |       |
| bit val |bit:val|bit:val|bit:val|bit:val|bit:val|bit:val|
|---------|-------|-------|-------|-------|-------|-------|
|  0   1  | 1   1 | 1   1 | 1   1 | 1   1 | 0   0 | 0   0 |
|  1   2  | 2   1 | 0   0 | 0   0 | 1   2 | 1   2 | 1   2 |
|  2   4  | 0   0 | 4   1 | 0   0 | 1   4 | 1   4 | 1   4 |
|  3   8  | 0   0 | 0   0 | 0   0 | 0   0 | 0   0 | 0   0 |
|---------|-------|-------|-------|-------|-------|-------|
|sum:     |     3 |     5 | ==> 1 |     7 |     6 |     6 |
|---------|-----------------------------------------------|
|check:   |  3 & 5 == 1 ? YES     |                       |
|         |  3 & 5 >= 1 ? YES     | ==>  3 & 5 > 0 ? YES  |
|=========================================================|

|------------------ value: 4 / mask: 5 -------------------|
|         | value | mask  |  and  |  or   |  xor  | xnor  |
|         |   4   |   5   | 4 & 5 | 4 | 5 | 4 ^ 5 |       |
| bit val |bit:val|bit:val|bit:val|bit:val|bit:val|bit:val|
|---------|-------|-------|-------|-------|-------|-------|
|  0   1  | 0   0 | 1   1 | 0   0 | 1   1 | 1   1 | 1   1 |
|  1   2  | 0   0 | 0   0 | 0   0 | 0   0 | 0   0 | 0   0 |
|  2   4  | 4   1 | 4   1 | 1   4 | 1   4 | 0   0 | 0   0 |
|  3   8  | 0   0 | 0   0 | 0   0 | 0   0 | 0   0 | 0   0 |
|---------|-------|-------|-------|-------|-------|-------|
|sum:     |     4 |     5 | ==> 4 |     5 |     1 |     1 |
|---------|-----------------------------------------------|
|check:   |  4 & 5 == 1 ? NO      |                       |
|         |  4 & 5 >= 1 ? YES     | ==>  4 & 5 > 0 ? YES  |
|=========================================================|


Please pay particular attention to the following checks:

|---------|-----------------------------------------------|
|check:   |  3 & 5 == 1 ? YES     |                       |
|         |  3 & 5 >= 1 ? YES     | ==>  3 & 5 > 0 ? YES  |
---------|------------------------------------------------|
|check:   |  4 & 5 == 1 ? NO      |                       |
|         |  4 & 5 >= 1 ? YES     | ==>  4 & 5 > 0 ? YES  |
|---------|-----------------------------------------------|


The output above is created by the following snippet:

( # start a sub-shell to encapsulate vars for easy copy-paste into the terminal
  # do not use this in a script!
echo ;
for o in 3 4; do
echo "|------------------ value: $o / mask: 5 -------------------|";
echo "|         | value | mask  |  and  |  or   |  xor  | xnor  |";
echo "|         |   $o   |   5   | $o & 5 | $o | 5 | $o ^ 5 |       |";
echo "| bit val |bit:val|bit:val|bit:val|bit:val|bit:val|bit:val|";
echo "|---------|-------|-------|-------|-------|-------|-------|";
mask=0;
for (( bit=0; bit<4; bit++)); do
    mask=$((1<<bit));
    echo -n "|  $bit   $mask "
    echo -n " | $(($o&mask))   $((($o&mask)>0))"
    echo -n " | $((5&mask))   $(((5&mask)>0))"
    echo -n " | $(((($o&mask)&(5&mask))>0))   $((($o&mask)&(5&mask)))"
    echo -n " | $(((($o&mask)|(5&mask))>0))   $((($o&mask)|(5&mask)))"
    echo -n " | $(((($o&mask)^(5&mask))>0))   $((($o&mask)^(5&mask)))"
    echo -n " | $(((($o&mask)^(5&mask))>0))   $((($o&mask)^(5&mask)))"
    echo " |";
done ;
echo "|---------|-------|-------|-------|-------|-------|-------|";
echo -n "|sum:     |     $o |     5 |";
echo " ==> $(($o&5)) |     $(($o|5)) |     $(($o^5)) |     $(($o^5)) |";
echo "|---------|-----------------------------------------------|";
echo -n "|check:   |  $o & 5 == 1 ? $(((($o&5)==1))&&echo YES||echo "NO ")     ";
echo "|                       |";
echo -n "|         |  $o & 5 >= 1 ? $(((($o&5)>=1))&&echo YES||echo "NO ")     ";
echo "| ==>  $o & 5 > 0 ? $(((($o&5)>0))&&echo YES||echo "NO ")  |";
echo "|=========================================================|";
echo ;
done
echo ;
)


Hello World!

... or: "Rise and Shine!"

So how do we do it now?! Let's imagine, we have the following option set:

# option set:
option_1=1
option_2=2
option_3=4
option_4=8
option_5=16
option_6=32
option_7=64
option_8=128
option_9=256


We could for example set a selection of those options in a function and return the combined code as a return value. Or the other way round: pass a selection of options as one numeric parameter. Whatever your use case is, the options would be summed together:

# set options:
option = option_1
       + option_4
       + option_5
       + option_9


How do we best check, which options are set (1,4,5, & 9)? It depends of course again on your use case, but i kinda like the case construct! Something like ...

case $option in
  0) echo "no option set!";;
  1) echo "option 1";;
  2) echo "option 2";;
  3) echo "option 1 and 2";;
  *) echo "...";;
esac

could work, but is not very nice, as we would have to construct every combination!


And the winner is ...

... or: "The Real Case"

We can use case in the following way instead ...

echo "check for options using >0:"
case 1 in
  $(( (option & option_1) >0 )) ) echo "- option_1 is set";;&
  $(( (option & option_2) >0 )) ) echo "- option_2 is set";;&
  $(( (option & option_3) >0 )) ) echo "- option_3 is set";;&
  $(( (option & option_4) >0 )) ) echo "- option_4 is set";;&
  $(( (option & option_5) >0 )) ) echo "- option_5 is set";;&
  $(( (option & option_6) >0 )) ) echo "- option_6 is set";;&
  $(( (option & option_7) >0 )) ) echo "- option_7 is set";;&
  $(( (option & option_8) >0 )) ) echo "- option_8 is set";;&
  $(( (option & option_9) >0 )) ) echo "- option_9 is set";;&
esac

Please note that

  • the spaces within $(( (option & option_1) >0 )) ) are completely optional and are added for readability. The last closing bracket ) is for the case construct.
  • the command-lists are terminated with ;;&, in order to continue evaluation with the next option test. In case you want to abord further processing of the list, set option=0 or to your current option=option_X.

... we get the following result:

check for options using >0:
- option_1 is set
- option_4 is set
- option_5 is set
- option_9 is set

Hurray! :-)


And if I was a rich man!

echo "# to use it in an if statement:";
if (((option&option_5)>0)); then
  echo "- option_5 is set"    
else
  echo "- option_5 is NOT set"
fi

# to use it in an if statement:
- option_5 is set


But finally the poor man's condition:

echo "# or to use it just for conditional execution:";
(((option&option_6)>0)) \
&& echo "- option_6 is set" \
|| echo "- option_6 is NOT set"

# or to use it just for conditional execution:
- option_6 is NOT set


"He who does not answer the questions has passed the test."

... or: 'Stay,' he said, 'that was only a test.'

(Kafka, 1975: 181)

So it would be perfectly possible to use the solution as posted in the question, by the slight change as follows:

(
declare -A releases=([token]=4)
declare -i MASK=5

if [[ $(( releases["token"] & $MASK )) -gt 0 ]]; then
  echo YES
else
  echo NO
fi
)

The changes are the following: - use $(()) instead of () to do the test, which will return the value of the bitwise comparison - use -gt 0 instead of -eq 1


altogether in action:

( # start a sub-shell to encapsulate vars for easy copy-paste into the terminal
  # do not use this in a script!
echo ;

for ((i=0; i<9;i++)); do
    o="option_$((i+1))"
    declare -i $o=$((1<<$i))
    echo "$o = ${!o}"
done

echo ;
echo ;

echo "# set options:"
echo "option = option_1"
echo "       + option_4"
echo "       + option_5"
echo "       + option_9"

option=option_1+option_4+option_5+option_9

echo ;
echo ;

echo "check for options using >0:"
case 1 in
  $(( (option & option_1) >0 )) ) echo "- option_1 is set";;&
  $(( (option & option_2) >0 )) ) echo "- option_2 is set";;&
  $(( (option & option_3) >0 )) ) echo "- option_3 is set";;&
  $(( (option & option_4) >0 )) ) echo "- option_4 is set";;&
  $(( (option & option_5) >0 )) ) echo "- option_5 is set";;&
  $(( (option & option_6) >0 )) ) echo "- option_6 is set";;&
  $(( (option & option_7) >0 )) ) echo "- option_7 is set";;&
  $(( (option & option_8) >0 )) ) echo "- option_8 is set";;&
  $(( (option & option_9) >0 )) ) echo "- option_9 is set";;&
esac

echo ;
echo ;

echo "# to use it in an if statement:";
echo "  => if (((option&option_5)>0));";
if (((option&option_5)>0)); then
    echo "- option_5 is set"    
else
    echo "- option_5 is NOT set"
fi

echo ;
echo ;

declare -A releases=([token]=4)
declare -i MASK=5

echo "# Does 4 pass the mask 5 in the test construct?";
echo "  => if [[ $(( releases["token"] & $MASK )) -gt 0 ]];";
if [[ $(( releases["token"] & $MASK )) -gt 0 ]]; then
    echo YES
else
    echo NO
fi

echo ;
echo ;

echo "# or to use it just for conditional execution:";
echo "  => (((option&option_6)>0)) && do || dont";
(((option&option_6)>0)) \
&& echo "- option_6 is set" \
|| echo "- option_6 is NOT set"

)


The End

Exit 0


回答3:

In if statement:

if (((releases["token"] & $MASK) == 1)); then 
  echo YES
else 
  echo NO
fi