4

I am having difficulty getting a for loop to take user input in the form of an argument to a function I created.

#!/bin/bash
number1="$1"    #Assume user input 5
loops="$2"      #Assume user input 5
if [[ ${number1} && ${loops} =~ ^-?[0.00-9.99]+$ ]];then
   for number2 in {1..${loops}}
   do
       Product=$(echo "${number1} * ${number2}" | bc -l)
       echo "${number1} * ${number2} = ${Product}"
       if test ${number2} -eq 7;then
           { echo "----------------------"; echo "${number2} is the best number."; echo "----------------------"; continue; }
       elif test ${number2} -eq 11;then
           { echo "------------------------------"; echo "${number2} is pretty awesome as well."; echo "------------------------------"; continue; }    
       fi 
   done
else
   { echo "Please try again with a valid numerical entry."; exit 1; }
fi

This script does not run when the user inputs an integer for the variable "loops", and any number for "number1". number1 = 5 in this case. It returns the error:

(standard_in) 1: syntax error

5 * {1..5} =

/home/$USER/For_Loop_Multiplication.sh: line 9: test: {1..5}: integer expression expected

/home/$USER/For_Loop_Multiplication.sh: line 11: test: {1..5}: integer expression expected

If I instead directly input an end bound on the number of loops directly into the script like the following, and number1 = 5, it runs fine:

#!/bin/bash
number1="$1"     #Assume user input 5
if [[ ${number1} =~ ^-?[0.00-9.99]+$ ]];then
   for number2 in {1..5}
   do
       Product=$(echo "${number1} * ${number2}" | bc -l)
       echo "${number1} * ${number2} = ${Product}"
       if test ${number2} -eq 7;then
           { echo "----------------------"; echo "${number2} is the best number."; echo "----------------------"; continue; }
       elif test ${number2} -eq 11;then
           { echo "------------------------------"; echo "${number2} is pretty awesome as well."; echo "------------------------------"; continue; }    
       fi 
   done
else
   { echo "Please try again with a valid numerical entry."; exit 1; }
fi

It returns the result:

5 * 1 = 5

5 * 2 = 10

5 * 3 = 15

5 * 4 = 20

5 * 5 = 25

Essentially what I think is happening is that the script seems to be placing the entire range as the value number2, which is defined as the iterator in the for loop. Since the input for the variable "loops" is an integer, there should be no difference between asking the user for a range and placing it in the loop, or simply setting it as a default when I write the script. However, there apparently is a difference... Any insight into solving this problem is appreciated. Thanks.

8
  • 2
    Adding to terdon's answer, the first test doesn't probably do what you think. [[ ${number1} && ${loops} =~ ^-?[0.00-9.99]+$ ]] means: if ${number1} expands to anything other than an empty string and ${loops} matches an optional dash followed by one or more characters matching a dot or a digit in the range 0-9, then execute the block below. $number1 will always expand to something as long as an argument was passed,not sure if that's intended, but the regex is wrong. You should check if ${loops} is in the 0.00-9.99 range in another way, there's no easy way to do that using regexes.
    – kos
    Dec 24, 2015 at 9:46
  • You're right. I thought that the logic above would pass that constraint on both "number1" and "loops". I'll play around with it and see if some added parenthesis or a new construction will help.
    – mike3759
    Dec 24, 2015 at 10:30
  • Additionally, why is "^-?[0.00-9.99]+$" a bad way to test this? My understanding is that this will pass positive or negative decimals in the range 0.00-9.99 because of the optional dash. I'm going off a construction from the following link that I edited slightly: stackoverflow.com/questions/2210349/…
    – mike3759
    Dec 24, 2015 at 10:46
  • 1
    Read the title carefully: "BASH: Test whether string is valid as an integer?" :). Whatever is the regex flavour you're using, you can't specify decimal ranges, you'll need more complex constructions for that (for example [0-9](.[0-9][0-9]?)? should match a number in that range, didn't test though). I.e. 0.00-0.99 is not a valid range, you can only specify ranges of integers (0-1, 2-3, 4-5, 0-5 and so on).
    – kos
    Dec 24, 2015 at 10:58
  • 1
    Technically [0.00-9.99] means: match a 0 digit or a dot or a 0 digit or a digit in the range 0-9 or a dot or a 9 digit or a 9 digit (which sums up to: match a digit or a dot).
    – kos
    Dec 24, 2015 at 11:02

1 Answer 1

7

The problem is this line:

for number2 in {1..${loops}}

This is a question of precedence. As explained in man bash:

The order of expansions is: brace expansion; tilde expansion, parameter and variable expansion, arithmetic expansion, and command substitution (done in a left-to-right fashion); word splitting; and pathname expansion.

So, brace expansion happens before variable expansion. Therefore, when the braces are expanded, $loops has not been evaluated to 5. This means that {1..$loops} (you don't need the second {}) expands to the string {1..5} and not the sequence of numbers from 1 to 5. The same thing happens if you use a string instead of a variable:

$ echo {1..5}  ## works: prints a sequence
1 2 3 4 5
$ echo {1..foo} ## fails: prints a string with no expansion
{1..foo}

As a workaround, you can use seq:

for number2 in $(seq 1 "$loops"); do ...

Or, even better, the right loop for the job:

for((number2=0;number2<=$loops; number2++)); do ...


Note: As a general rule, you should also always quote your variables.

1
  • The second option worked for me. Thanks for clearing that up, I remember reading about the order of expansions, but didn't think to apply it here.
    – mike3759
    Dec 24, 2015 at 10:28

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .