NeuroAgent

Bash Variable Range Loops: Best Methods

Learn the best methods to iterate over dynamic number ranges in Bash using variables. Discover C-style loops, seq command, and safe alternatives with practical examples.

Question

How do I iterate over a range of numbers defined by variables in Bash?

I know I can use a sequence expression to iterate over a range of numbers in Bash:

bash
for i in {1..5}; do echo $i; done

This outputs:

1
2
3
4
5

However, when I try to replace either endpoint with a variable, it doesn’t work as expected:

bash
END=5
for i in {1..$END}; do echo $i; done

Instead of iterating from 1 to 5, this prints:

{1..5}

What is the correct way to iterate over a range of numbers in Bash when the range is defined by variables?

NeuroAgent

To iterate over a range of numbers defined by variables in Bash, you need to use alternative methods since brace expansion doesn’t work directly with variables. Here are the most effective approaches:

Contents

Using the seq Command

The seq command is the most straightforward solution for generating number sequences in Bash. It’s available on most Linux systems and macOS.

bash
#!/bin/bash
START=1
END=5

for i in $(seq $START $END); do
    echo $i
done

This will correctly output:

1
2
3
4
5

Advantages:

  • Readable and intuitive
  • Works with both start and end variables
  • Supports step size: seq 1 2 10 (1 to 10 with step 2)
  • Compatible across different shell implementations

Note: The $(seq ...) syntax creates word splitting, which can be problematic with numbers that have leading zeros or contain spaces. For more robust handling, use the -s option with a custom separator:

bash
for i in $(seq -s " " $START $END); do
    echo $i
done

C-Style For Loop

Bash supports C-style for loops with arithmetic evaluation, which is often the most reliable method for numerical iteration:

bash
#!/bin/bash
START=1
END=5

for ((i=START; i<=END; i++)); do
    echo $i
done

This outputs the same 1-5 range as expected.

Advantages:

  • No external command dependencies
  • Excellent performance with large ranges
  • Supports complex arithmetic operations
  • More predictable behavior with edge cases

Common patterns:

bash
# Counting backwards
for ((i=END; i>=START; i--)); do
    echo $i
done

# With custom step size
for ((i=START; i<=END; i+=2)); do
    echo $i
done

Using eval (Carefully)

While eval can solve the problem, it comes with security risks and should be used with caution:

bash
#!/bin/bash
START=1
END=5

eval "for i in {$START..$END}; do echo $i; done"

Why this works: eval re-parses the command string after variable substitution, allowing brace expansion to work on the expanded values.

Security Warning: Never use eval with untrusted input, as it can execute arbitrary code. Only use this method when you have full control over the variable values.

Safer alternatives with eval:

bash
# Using arithmetic expansion
for i in $(eval echo "{$START..$END}"); do
    echo $i
done

# With word splitting prevention
IFS=$'\n' read -r -d '' -a numbers < <(eval echo "{$START..$END}")
for i in "${numbers[@]}"; do
    echo $i
done

Best Practices and Recommendations

Method Comparison

Method Readability Performance Safety Portability
seq command High Medium High Good
C-style for loop High High High Excellent
eval Medium High Low Good

Recommended Approach

For most use cases, the C-style for loop is the best choice because it:

  • Doesn’t require external commands
  • Is safe from security vulnerabilities
  • Offers excellent performance
  • Works consistently across different Bash versions

When you need to generate sequences for other commands or when readability is paramount, the seq command is a good alternative.

Advanced Example: Dynamic Range Handling

bash
#!/bin/bash
generate_range() {
    local start=$1
    local end=$2
    local step=${3:-1}
    
    # Handle invalid ranges gracefully
    if (( start > end && step > 0 )) || (( start < end && step < 0 )); then
        return 1
    fi
    
    # Use C-style loop for safety and performance
    for ((i=start; i<=end; i+=step)); do
        echo $i
    done
}

# Usage examples
echo "1 to 5:"
generate_range 1 5

echo "10 to 1 (descending):"
generate_range 10 1 -1

echo "Even numbers 2 to 10:"
generate_range 2 10 2

Handling Edge Cases

Be aware of these potential issues:

  1. Variable expansion order: Always ensure variables are properly quoted when used in contexts where word splitting might occur.

  2. Large ranges: Very large ranges can cause performance issues. Consider using seq with the w option for better memory handling.

  3. Non-integer values: These methods assume integer values. For floating-point ranges, you’ll need additional processing.

  4. Portability: The C-style for loop is Bash-specific. For POSIX-compliant scripts, use seq or arithmetic expansion with while loops.


Sources

  1. Bash Reference Manual - Looping Constructs
  2. Linux seq Command Manual
  3. Advanced Bash-Scripting Guide - For Loops
  4. Stack Overflow - Bash variable ranges
  5. Bash Hackers Wiki - Arithmetic Expansion

Conclusion

  • The C-style for loop (for ((i=START; i<=END; i++))) is the most reliable and secure method for numerical iteration in Bash
  • The seq command provides good readability and works well when you need sequences for other commands
  • Avoid eval unless you have complete control over the variable values due to security risks
  • Always consider edge cases like invalid ranges, large numbers, and portability requirements
  • For complex scenarios, create a reusable function that handles various range parameters safely

Choose the method that best fits your specific needs, prioritizing safety and readability in your scripts.