0

I've found this simple bash that illustrates my problem:

  #!/bin/sh 
str1="Learn Bash"
str2="Learn Bash"

if (( "$str1" == "$str2" )); then echo "Both Strings are Equal." else echo "Both Strings are not Equal." fi

And the result is:

    if_equal.sh: 6: if_equal.sh: Learn Bash: not found

Both Strings are not Equal.

Basically I want the if to compare the result of awk with a float once, and a integer given by user after, so when I can, I use

[ "$c" -gt "$o" ] 

but it not works with floats neither I can loop it with a third variable like this:

[ "$c" -gt "$o" ] && echo fine || [ "$b" -gt "$o" ] && echo better

I've checked all spaces, tried with and without ", [],(),(())... I can't find why if finds that the first variable is missing. BTW my script created a file '0.9500' since I ran it with

if (( "$c" > "0.9500" ))

maybe there is the workaround?

Mona_Lu
  • 17

2 Answers2

10

In bash, (( ... )) is an arithmetic evaluation. In the sh shell, which is the shell your script is using due to the #!-line, it is most likely being interpreted as two nested sub-shells. This is why you get a file called 0.9500 when you run (( "$c" > "0.9500" )) with the sh shell. The > is an I/O redirection, not a comparison operator, and $c would be interpreted as a command, which is what's causing your "not found" error message.

In the sh shell, if you want to compare strings, use

[ "$str1" = "$str2" ]

If you want to compare integers, use, e.g.,

[ "$num1" -gt "$num2" ]

In the bash shell, you may use the above syntax too, but could you could also use

[[ "$str1" == "$str2" ]]

to compare two strings for equality, and

(( num1 > num2 ))

to compare two integers.

The bash shell does not include the capability to treat floating-point numbers in any other way than as strings.

To compare floating-point numbers in the shell, you may use awk.

if awk -v num1="$num1" -v num2="$num2" 'BEGIN { exit !(num1 > num2) }'
then
    printf '%s is greater than %s\n' "$num1" "$num2"
else
    printf '%s is not greater than %s\n' "$num1" "$num2"
fi

Note that the boolean result of the comparison must be inverted with ! to convert it to an exit-status for the shell.

Related:

Kusalananda
  • 333,661
  • "[[ "$str1" == "$str2" ]]" So I can compare strings only for their equality or not? So I've made awk print the result as integer like in the first answer here and then I use [ "$num1" -gt "$num2" ]. Thank you – Mona_Lu Oct 02 '21 at 08:29
  • 1
    @Mona_Lu You were consistently using a greater-than in your question when comparing numbers, which is why I wrote my answer using the same comparison with numbers. To compare numbers for equality, use -eq in [ ... ] or [[ ... ]], or use == in (( ... )). – Kusalananda Oct 02 '21 at 08:36
  • 1
    @Mona_Lu If you just want to chop off the decimals of some number $num, use ${num%.*} (assuming the number in $num uses dot as the decimal point). [ "${num1%.*}" -gt "${num2%.*}" ]. This was not an issue in your question, so I never mentioned it. – Kusalananda Oct 02 '21 at 08:38
  • My strings are float numbers like 0.9500 and 0.9378 and I want to compare if the first is greater than the second. If I got it right, I can't use [[ "$str1" >> "$str2" ]] and (( "$c" > "0.9500" )) is not accepted, so I have to work with integers or awk as @roaima mentioned below. – Mona_Lu Oct 02 '21 at 08:57
  • @Mona_Lu See update to the question. As I can't see your own awk code, you may want to do something similar within your existing awk program rather than using two shell variables to hold the intermediate numbers. – Kusalananda Oct 02 '21 at 09:16
  • 1
    Technically, in the POSIX sh language, ( (...)) is a nested subshell, but ((...)) is unspecified. In practice, some sh interpreter implementations either behave like ksh and treat it as arithmetic expression evaluation, while some others treat it as nested subshell. – Stéphane Chazelas Oct 02 '21 at 11:34
  • @StéphaneChazelas Thanks. I modified my language slightly, inserting "most likely". I'm assuming the user ended up using dash, which treats (( ... )) as a nested subshell. – Kusalananda Oct 02 '21 at 12:07
6

First of all, sh doesn't handle (( … )). You could use bash, but even then it's only for numeric integer expressions. See later in this answer for sh-compatible use of awk. I note that you describe your script as a bash script but you've used #!/bin/sh as the header. This header declares the script as a sh script, which has slightly different syntax.

If you're using bash you should search the documentation (man bash) for (( (you might need to escape the brackets, i.e. \(\(, if your pager requires it). You'll find this text,

((expression)) The expression is evaluated according to the rules described below under ARITHMETIC EVALUATION.

Search down for ARITHMETIC EVALUATION and you'll eventually read this (my emphasis),

ARITHMETIC EVALUATION The shell allows arithmetic expressions to be evaluated, under certain circumstances (see the let and declare builtin commands, the (( compound command, and Arithmetic Expansion). Evaluation is done in fixed-width integers with no check for overflow [...]

What this is telling you is that the (( … )) construct can evaluate integer arithmetic. It's not for strings or for floating point arithmetic.

Continuing now with an answer for either sh or bash.

You can handle strings with [ … ] or [[ … ]],

if [[ "$str1" == "$str2" ]] …    # bash

or

if [ "$str1" = "$str2" ] …       # POSIX including sh, and bash

Floating point arithmetic is harder; you have to drop out to bc or awk for this (my preferred approach is awk):

a=12.34 b=5.678    # These are strings

You cannot compare them as integers, because they're floats

if [[ "$a" -gt "$b" ]]; then echo yes; else echo no; fi -bash: [[: 12.34: syntax error: invalid arithmetic operator (error token is ".34")

You can use awk though

if awk -v a="$a" -v b="$b" 'BEGIN { exit !(a > b) }'; then echo yes; else echo no; fi yes

Here, we assign two awk variables a and b. Before awk has a chance to start reading from stdin we compare them numerically with > and exit with a status that corresponds to true/false. That awk exit status is processed by the shell's if … then … else … fi construct as usual.

Chris Davies
  • 116,213
  • 16
  • 160
  • 287
  • In fact i modified the header after reading other questions here, before it was #!/bin/bash... The theory is not my strong, I pick solutions here and there (especially here) and I note your awk method for the next time! – Mona_Lu Oct 02 '21 at 08:23
  • @Mona_Lu you don't need add complexity by using awk to print an integer result and then putting that value into [ x -gt y ]. Just use the awk directly as in my (last) example – Chris Davies Oct 02 '21 at 08:36
  • In my next script for sure, but in this one it was more simple to add printf "%.0f\n" few times, than edit the if conditions this few times. So thank you for this method! – Mona_Lu Oct 02 '21 at 08:46