Chris Jansson
Chris Jansson

Reputation: 65

Bash script - if statement inconsistency

I'm learning how to write scripts, and am having an issue with an if statement. The script reads in user input for their scores, and calculates their average grade. However, if the user enters a score lower than 0 or greater than 100, the script should ask them to re-enter a valid value. Here is the code below:

#!/bin/bash

SCORE=0
AVERAGE=0
SUM=0
NUM=0

while true
do
    # Read score from user
    echo -n "Enter your score [0-100] ('q' for quit): "; read SCORE

    # Validate user input
    if (($SCORE < 0)) || (($SCORE > 100))
    then
        echo "Invalid input, try again: "
    #elif (( $SCORE == "q" )) 
    elif [ "$SCORE" == "q" ]
    then
        echo "Average rating: $AVERAGE%."
        break
    else
        # Perform calculation
        SUM=$[$SUM + $SCORE]
        NUM=$[$NUM +1]
        AVERAGE=$[$SUM / $NUM]
        echo "Average rating: $AVERAGE%"
    fi
done

Notice I have the elif commented out. When I write that elif with [] notation, it works as expected. However, if I switch to the elif which uses (()), and enter "00" as my score, the program executes the "then" block on and exits. Why is it doing that? 00 is not 'q'.

Upvotes: 1

Views: 224

Answers (1)

Inian
Inian

Reputation: 85865

The line (( $SCORE == "q" )) does not work for the exact reason you are suggesting. The ((..)) is only meant for arithmetic operations and string operations cannot be done within it. You need to use the test operator [ or [[ for it.

Quoting from the GNU bash documentation

6.5 Shell Arithmetic

The shell allows arithmetic expressions to be evaluated, as one of the shell expansions or by using the (( compound command, the let builtin, or the -i option to the declare builtin.

Evaluation is done in fixed-width integers with no check for overflow, though division by 0 is trapped and flagged as an error. The operators and their precedence, associativity, and values are the same as in the C language.

Some of the standard expression involving strings when used with the classic test operator ([)

Operator                         Syntax-Description
-------------------------------------------------------------------------------------------
-z <STRING>                True, if <STRING> is empty.
-n <STRING>                True, if <STRING> is not empty (this is the default operation).
<STRING1> = <STRING2>      True, if the strings are equal.
<STRING1> != <STRING2>     True, if the strings are not equal.

The one you have used [ "$SCORE" == "q" ] is synonymous to using [ "$SCORE" = "q" ] in a way only literal string match is performed but not POSIX compatible as the latter.

The == comparison operator behaves differently within a double-brackets than within single brackets (both literal & glob matching)

[[ $a == z* ]]   # True if $a starts with an "z" (pattern matching).
[[ $a == "z*" ]] # True if $a is equal to z* (literal matching).

Also you could re-write your statements entirely in arithmetic context using the ((..)) operator over the legacy $[ ... ] operator.

sum=$((sum + score))
((num++))
(( num != 0 )) && average=$(( sum / num ))

printf "%0.2f\n" $num

Note that I have lowercased the variable names to distinguish them from the environment variables (shell system variables) but just as user define variables. And used printf over echo to introduce POSIX guaranteed floating point formatting options which echo does not allow.

Upvotes: 5

Related Questions