1

I am trying to write a script that reads three integers, then checks if the sum of any two of those numbers is greater than the third one. If that is true, then it checks if those numbers are equal, and prints a message. If not, it checks whether any two of the numbers are equal and prints another message. If all of the above are false, it prints a message saying that all numbers are different. I have tried to put that in the following nested conditional:

read X
read Y
read Z
if [ $X + $Y > $Z ] && [ $X + $Z > $Y ] && [ $Y + $Z > $X ]
then
    if [ $X = $Y = $Z ]
    then
        echo "All numbers are equal."
    elif [ [ $X = $Y ] && [ $X != $Z ] ] || [ [ $X = $Z ] && [ $X != $Y ] ] || [ [ $Z = $Y ] && [ $X != $Y ] ]
    then
        echo "Two of the numbers are equal."
    else
        echo "None of the numbers is equal to another."
    fi
fi

I have tried all types of combinations with brackets and parentheses (the above is just one of them), but none of them has worked so far. I have already taken a look at related posts:

Bash if statement with multiple conditions throws an error

Bash: Two conditions in if

How to represent multiple conditions in a shell if statement?

but I haven't found any that are covering conditions with arithmetic operators in them. Can anyone please tell me what is the right way?

(Edit: I forgot to mention in the original post that I am new to bash, so please excuse me for any profound mistakes I might have made. I am still trying to figure out how things are working.)

codeforester
  • 39,467
  • 16
  • 112
  • 140
Ioannis
  • 25
  • 2
  • 8
  • See: `help test`. `[ ... ]` is a synonym for the `test` builtin. – Cyrus Apr 23 '17 at 16:04
  • You realise in the first `if` that you only need to check two of the numbers are greater than another once, you don't need to check every combination. – 123 Apr 23 '17 at 17:00
  • @123 Yes, you are right, but I guess after some point, I was more interested in finding out if a condition like that could work and how that could be achieved. – Ioannis Apr 23 '17 at 17:54

4 Answers4

4

Here is a variation on Inian's answer taking advantage of arithmetic operations not requiring $ to expand variables, them accepting logical operators, and the fact that the first test for equality of all numbers allows the following test to be simplified.

Please note that checking if the values read actually are integer would be a good idea to avoid unexpected behavior.

#!/bin/bash
read X
read Y
read Z
if
  (( X+Y>Z || X+Z>Y || Y+Z>X ))
then
  if
    (( X==Y && Y==Z ))
  then
      echo "All numbers are equal"
  elif
    (( X==Y || X==Z || Z==Y ))
  then
    echo "Two of the numbers are equal"
  else
    echo "All three numbers are different"
  fi
fi

The $(( )) for of arithmetic expression expands to the result of the evaluation of the expression found inside. The (( )) for acts as a command that returns 0 if the expression is a test that results in a "true" value OR if it evaluates to a non-zero number, and a non-zero value otherwise. This second form is very useful for tests.

As an aside, I like using the properties of (( )) to handle on/off options in scripts. For instance, ((state_variable)) will evaluate to "false" if the variable is null or 0, and "true" otherwise, which maps nicely to how such a variable is intuitively expected to behave.

Fred
  • 6,590
  • 9
  • 20
  • This is a real good simplification of OP's attempt, tried to _stick_ to OP's logic in my answer, but this looks neat! `++` – Inian Apr 23 '17 at 16:49
  • Both you and @Inian understood what I had in mind, but you were a bit closer to what I initially intended to do. I guess I mixed both syntaxes with `(( ))` and `[[ ]]`. Could you please explain again what you are describing in the first paragraph after your code, what the `(( ))` acts as? So, if I understood correctly, it returns either 0 if it evaluates to a non-zero value, or a non-zero number if it evaluates to 0? Thank you again for your help. – Ioannis Apr 23 '17 at 19:32
  • @Ioannis `(( ))` does what you said when it evaluates to a number, but if it contains a test (i.e. `==`, `!=`, `<`...), then it returns `0` if the condition is met, and non-zero otherwise. Essentially, it is a different type of test that can be used like `[[ ]]` for conditions in `if`/`while` statements and anywhere else you would use the return code of a command. – Fred Apr 23 '17 at 23:15
  • 1
    @Ioannis Please note, however, that `(( ))` can be used for assignments, so it can have side effets (contrary to `[[ ]]`). An assignment would be of the form `(( varname = value ))`. The equality test is `==` (contrary to `[[ ]]` which accepts both `=` and `==` to test equality), so that can be confusing a bit if you do not use it often. – Fred Apr 23 '17 at 23:18
2

You could also do it a different way by just incrementing for matches and using a case statement.

Should make it easier to scale with more variables as well.

#!/bin/bash

read X
read Y
read Z

((Matches+=(X==Y)))
((Matches+=(Y==Z)))
((Matches+=(X==Z)))

case "$Matches" in

0) echo "None of the numbers is equal to another.";;
1) echo "Two of the numbers are equal.";;
3) echo "All numbers are equal.";;

esac
123
  • 10,778
  • 2
  • 22
  • 45
  • 1
    Can you please explain what the `((Matches+=(..)))` part is doing? I am unfamiliar with that syntax. I suppose it's doing something similar to @Cyrus 's approach..? Creating an array with the numbers provided by the user or something? I would appreciate if you have the time at some point to explain a bit. Thank you again for answering. – Ioannis Apr 23 '17 at 19:16
  • @Ioannis Nope just a single variable, it just adds the result of the condition `((X==Y))` which is `1` for `true` and `0` for `false` to the var `Matches`. I then just check the value in the var in the case statement. Cyrus's answer is using an associative array and the fact that every key must be unique, so when the same number is added twice, there is still only one key in the array, then they just check the number of keys in the array. – 123 Apr 23 '17 at 19:22
  • So, does the var `Matches` add the results of each test, and the `0) 1) 3)` correspond to the value of the var, and it prints out the message accordingly? – Ioannis Apr 23 '17 at 19:40
  • @Ioannis Yes it increments it by the result each time. There is no 2, as if two of the statements are true then the third must also be true. – 123 Apr 23 '17 at 19:41
  • This relies on "false" being 1 (and not some other non-zero value). Is that a reliable assumption? – Fred Apr 24 '17 at 10:19
  • @Fred It relies on false being zero, and yes it is. – 123 Apr 24 '17 at 10:29
  • @123 Yes, got it mixed up. By "reliable", I mean "specified by POSIX" (or some other spec that can be considered stable through time) rather than "known versions of Bash use 1 for true and 0 for false in arithmetic expressions". Do you know if it is the case? – Fred Apr 24 '17 at 10:34
  • `let` i.e `(())` isn't specified by POSIX, although your second statement is correct that it is that way in all known versions. Also since it isn't a return code so there is no logical reason for it to change well known consistent functionality which is universal to almost all languages, I personally think it is a safe assumption to make. – 123 Apr 24 '17 at 10:52
2

A completely different approach to tackle the problem.

#!/bin/bash

declare -A a               # declare associative array a

read x; a[$x]=$x
read x; a[$x]=$x
read x; a[$x]=$x

case ${#a[@]} in
  1) echo "All numbers are equal." ;;
  2) echo "Two of the numbers are equal." ;;
  3) echo "None of the numbers is equal to another." ;;
esac
Cyrus
  • 84,225
  • 14
  • 89
  • 153
  • I cannot really say I understand what your script is doing. You lost me a bit. I think I know what is happening more or less, but could you please explain the syntax a bit more? Because the first is confusing, plus some operators that I do not know. I would really appreciate that. Thank you anyway for the different solution. I like learning new approaches to a problem. – Ioannis Apr 23 '17 at 19:11
  • As an introduction, I recommend an article on the subject [Associative Arrays](http://www.linuxjournal.com/content/bash-associative-arrays). – Cyrus Apr 23 '17 at 19:27
  • If you only use integers it is possible to remove `declare -A a` and use a normal array. – Cyrus Apr 23 '17 at 19:44
1

One-liner function, condensed from 123's answer:

n=(No Two "" All)
3a(){ echo "${n[$((($1==$2)+($1==$3)+($3==$2)))]} numbers are equal." ; }

Show all three cases:

3a 1 1 1 ; 3a 1 1 2 ; 3a 1 2 3
All numbers are equal.
Two numbers are equal.
No numbers are equal.
Community
  • 1
  • 1
agc
  • 7,973
  • 2
  • 29
  • 50