4

i try to validate if a number with decimals is between in a specify range, i mean like the following example :

rangeA=58.5
rangeB=61.5
number=62.7

 if [[ ( "$number" > "$rangeA" | bc ) || ( "$number" = "$rangeA" | bc ) ]] && [[ ( "$number" < "$rangeB" | bc ) || ( "number" = "rangeB" | bc ) ]]; then

but i'm stuck in this operation, I would appreciate your help thanks

Peter Cordes
  • 328,167
  • 45
  • 605
  • 847
shaveax
  • 458
  • 6
  • 16

4 Answers4

5

Bash's < and > compare strings, -lt and -gt compare integers, the only way to compare floating point numbers is to shell out to bc(1) (which you do, but you do it wrong):

rangeA=58.5
rangeB=61.5
number=62.7

if (( $(bc <<<"$number >= $rangeA && $number <= $rangeB") )); then
    echo yes
else
    echo no
fi

bc prints 1 or 0 to the standard output, and bash's arithmetics context (((expression))) tests it for zero and sets the status code accordingly.

eush77
  • 3,870
  • 1
  • 23
  • 30
  • thanks, both answer are great, the only doubt here is how to hide the standard output of bc ? – shaveax Nov 19 '15 at 06:17
  • @shaveax There's no bc output in my answer. – eush77 Nov 19 '15 at 06:19
  • sorry about that, my bad , thanks :) – shaveax Nov 19 '15 at 06:22
  • 1
    Don't encourage use of backticks. `$()` is a better choice for readability (and sometimes even correctness). http://mywiki.wooledge.org/BashFAQ/082. Also, bash implements `<<<` by writing the data to a file in `/tmp` and redirecting from that. `echo ...|bc` would be slightly more efficient. – Peter Cordes Nov 19 '15 at 07:16
  • @PeterCordes Re backticks. Yeah, I know `$()` is better, didn't think it matters in this case. Fixed it. Re `<<<`. I think it still communicates the intent better and the fact that bash implements it that way now can be improved in the future. Using `echo` is prone to its own set of problems. – eush77 Nov 19 '15 at 07:32
  • I misread the code initially, I thought you had `$rangeA` as the beginning of the string to be `echo`ed, in which case you know it doesn't start with a `-e` or something. Also, if you like parentheses, try an arithmetic context for boolean evaluation: `if (( $(bc <<< "...") )); then `. It's pretty clean for boolean variables, e.g. `if (( var ));then`, with a few caveats. – Peter Cordes Nov 19 '15 at 08:57
  • @PeterCordes ((expression)) is clearly the most elegant way. Thank you! – eush77 Nov 19 '15 at 09:51
4

You can use awk:

rangeA=58.5
rangeB=61.5
number=62.7

if awk -v number=$number -v rangeA=$rangeA -v rangeB=$rangeB  '
   BEGIN{exit !(number >= rangeA && number <= rangeB)}'
then
   echo "condition matched"
else
   echo "condition didn't match"
fi
anubhava
  • 761,203
  • 64
  • 569
  • 643
  • 1
    the second `||` should be a `&&` – steffen Nov 19 '15 at 06:18
  • Your example prints "condition matched" when the number is clearly out of range (even if the second `||` is replaced with `&&`). And there's also the output from `bc` (0s and 1s) left hanging. The general problem is that `bc` doesn't set the exit status depending on its result. – eush77 Nov 19 '15 at 06:20
  • Ah my bad, check updated answer now. – anubhava Nov 19 '15 at 06:42
1

bc can do complex tests on numbers using "and", &&, or "or", ||, logic. If the condition is true, bc echoes 1 to stdout. We can test for 1 using grep -q 1 where the -q option tells grep to be quiet and produce nothing on stdout:

if bc <<< "$number >= $rangeA && $number <= $rangeB" | grep -q 1
then
    echo True
else
    echo False
fi
John1024
  • 109,961
  • 14
  • 137
  • 171
1

Not sure if your if is a requirement, but you could also use the ternary operator in awk:

awk -v n=$number -v a=$rangeA -v b=$rangeB \
    'BEGIN{print(n >= a && n <= b)?"yes":"no"}'

Sort of an implied if-else.

userABC123
  • 1,460
  • 2
  • 18
  • 31