Bash for Loop Range Variable

Written by: Linuxopsys   |   Last updated: April 9, 2023

Bash supports for loops to repeat defined tasks, just like any other programming language. Using for loops, we can iterate a block of statements over a range of numbers to achieve the desired outcome. This set of numbers, to be supplied to the for loop, can be generated in multiple ways.

In this tutorial, we will understand different ways of using for loop over a range of numbers.

Range - For loop

Every for loop in Bash begins with the keyword 'do' followed by the commands inside the block. Similarly, it is closed by the 'done' keyword. The number of times a 'for loop' runs is dependent on the declared list of variables. After the first execution of the block of code or commands between 'do' and 'done', the loop chooses the next subsequent item from and repeats the complete cycle again.

The for loop can be used with a range of numbers, starting from the specified number, and incrementing or decrementing by a factor, and ending at a given number. The command-line tools like seq and eval can also be injected inside the for loop to generate a range of numbers for the loop to act upon.

1. Using range

We can use the for loop with the range syntax and indicate the first and last element. 

Syntax:

{START..END}

Example:

#!/bin/bash

echo "Forward"
for i in {1..10} 
do
    echo "$i"
done

echo "Reverse"
for i in {10..1} 
do
    echo "$i"
done
for loop range

The script outputs all the elements from the provided range using the brace expansion. This syntax works for elements in ascending if the starting number is lesser than the final number. It will work exactly in the opposite way and iterate in descending order if the starting number is greater than the final one. The numbers generated are in arithmetic progression with a common difference of 1 or -1, as per the scenario.

Let’s try to use the same example with variables. 

#!/bin/bash
a=1
b=10
for i in {$a..$b} 	#loop 1
do
    echo "$i"
done

As you can see, this has a major flaw. We cannot use this with variables. Why is it happening? This is because in bash, brace expansion is always performed before any other expansions. Any character special to other expansions are preserved in the result. Hence, brace expansion is done early as a purely textual macro operation, before parameter expansion.

The brace expansion in the first for loop is happening before $a and $b could be assigned values. As a result, the variable i holds just a single value "{$a..$b}"  making this loop run once. While echoing the variables $a and $b are then expanded, which prints "{1..10}".

2. Using range with increment or decrement factor 

From Bash Bash version 4.0+ we have the option to use the above method with a step count.

Syntax:

{START..END..FACTOR}

Example:

#!/bin/bash

echo "Forward"
for i in {1..10..2} 
do
    echo "$i"
done

echo "Reverse"
for i in {10..1..2} 
do
    echo "$i"
done

Using the increment or the decrement factor, we are able to make the loop iterate over a particular set of numbers in the range. If that factor is set to 1, we will achieve the same result as we did earlier. This approach can’t still be applied to variables as brace expansion will happen before the parameters could expand.

range with increment or decrement using for loop

The script is modifying the numbers in steps of 2. In the first iteration, the for loop takes the starting number and executes the statements inside the do-done block. In the next iteration, it will add or subtract the factor from the starting number and repeat those executions. This will continue until the iterator (i) is greater or lesser than the ending number

3. Using eval 

We can specify the variable range to the echo command and this will be inside the eval command.

Syntax:

eval echo "{$START..$END..$FACTOR}"

Example:

#!/bin/bash

echo "Forward"
for i in $(eval echo "{1..10}")
do
    echo "$i"
done

echo "Reverse"
for i in $(eval echo "{10..1..3}")
do
    echo "$i"
done

echo "With variables"
a=2
b=8
c=2
for i in $(eval echo "{$a..$b..$c}")
do
    echo "$i"
done

This approach works with the help of the eval command. eval provides an additional layer of evaluation before executing the command. echo  {$a..$b..$c} will be evaluated as a command by the eval. 

The shortcomings of not being able to use variables, as discussed in the above examples, are handled. This way we can avoid the problems faced with brace expansion and use it for ranges of integers and even use the numbers in both directions. 

for loop using eval with range

The iterator is getting assigned the value by the for loop. Inside this loop there is an eval command that which effectively breaks down the given expression to:
for i in 1 2 3 4 5 

After this, it is a simple for loop which works by picking the elements one by one and executing the statements or commands inside the do-done block.

4. Using seq

The seq command is short for sequence. It allows us to generate sequences of numbers within the given range. These numbers can be integers or floating point. We can embed this command inside the for loop.

Syntax:

seq $START $FACTOR $END

Example:

#!/bin/bash

echo "Forward"
for i in $(seq 1 10)
do
    echo "$i"
done

echo "With variables"
a=2
b=10
c=2.5
for i in $(seq $a $c $b)
do
    echo "$i"
done

The seq command helps to iterate over a range of numbers. The seq command can take the first, the factor and the final value as the input arguments. One important thing to note, which is also a major drawback, is that this command will not work if the first argument is larger than the final one.

for loop with seq range

We have developed a Bash script that uses the seq command to generate numbers for the loop. The sequence numbers can go in either direction. We can have used it with variables and took the step factor as a decimal number.  The script in the final for loop generated numbers from 2 to 10 with an increment of 2.5 every time.

5. Using conditions

This is a very common method, present in every programming language. 

Syntax:

for (( exp1; exp2; exp3 ))
do  
    …
    …
done

Where, 

  • exp1 = initializer 
  • exp2 = condition
  • exp3 = counter

Example:

#!/bin/bash

echo "Forward"
for (( i=1; i<=10; i++ )) 
do
    echo "$i"
done

echo "Reverse"
for (( i=10; i>=1; i-- )) 
do
    echo "$i"
done

The initial value of the iterator (i) is set to the starting number in the range. This is the first expression, exp1. The loop executes, as long as the condition in exp2 is true, which signifies that the maximum value cannot be more than the last number of the range. The exp3 corresponds to the step factor that increments or decrements it. 

using for loop condition

The script begins by initializing i=1. It checks the current value of i against 10. As it is less, the statement inside the do-done block executes, after which the i++ operation increments i to 2. As i is less than equal to 10, the same cycle repeats till 10. The moment i has been incremented to 11, the loop terminates.

Conclusion

  • Bash gives us different ways to iterate over a variable range of numbers.
  • We saw the drawbacks of the approaches and learned how to handle it.
  • We can automate different processes using loops over a numerical range.
SHARE

Comments

Please add comments below to provide the author your ideas, appreciation and feedback.

Leave a Reply

Leave a Comment