1

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

7
  • 1
    $[ ... ] is legacy syntax. Bash supports it for compatibility with ancient pre-POSIX shells (which is to say, shells from before 1991), but unlike $(( )), it isn't guaranteed to available on baseline-POSIX shells -- it doesn't work, for instance, with ash or dash. Commented Oct 3, 2017 at 19:02
  • 1
    And == isn't guaranteed to work inside [ ] either -- the POSIX-compliant string comparison operator is just =. If you want bashisms, use [[ ]] to make it explicit that you're relying on extended syntax rather than intending to use portable constructs. Commented Oct 3, 2017 at 19:04
  • 1
    Also, it's ideal to avoid all-caps names for your own variables -- see pubs.opengroup.org/onlinepubs/9699919799/basedefs/…, fourth paragraph: The name space of environment variable names containing lowercase letters is reserved for applications. Applications can define any environment variables with names from this name space without modifying the behavior of the standard utilities -- keeping in mind that setting a shell variable will overwrite any like-named environment variable. Commented Oct 3, 2017 at 19:06
  • 2
    You are not having problems with the if statement but with the command which follows it. The brackets are not exclusively part of the if syntax. (( ... )) and [[ ... ]] are compound commands whereas [ is a shell builtin. The syntax for if is actually if list then list fi. Commented Oct 3, 2017 at 19:10
  • 1
    nod. That mistake is why a lot of folks think they need something like if [ "$(grep -e content filename)" ]; then ... rather than the correct, much simpler, and faster-to-execute if grep -q -e content filename; then .... Commented Oct 3, 2017 at 19:24

1 Answer 1

5

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.

Sign up to request clarification or add additional context in comments.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.