How to use bitwise operators in if statements?

前端 未结 3 755
迷失自我
迷失自我 2021-02-07 08:33

I want to write something like this:

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

but I get the error that:

3条回答
  •  北海茫月
    2021-02-07 09:06

    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<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<


    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<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
    

提交回复
热议问题