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:
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:
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?
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.
#!/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:
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:
#!/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:
# 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:
#!/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:
# 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
#!/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:
-
Variable expansion order: Always ensure variables are properly quoted when used in contexts where word splitting might occur.
-
Large ranges: Very large ranges can cause performance issues. Consider using
seqwith thewoption for better memory handling. -
Non-integer values: These methods assume integer values. For floating-point ranges, you’ll need additional processing.
-
Portability: The C-style for loop is Bash-specific. For POSIX-compliant scripts, use
seqor arithmetic expansion withwhileloops.
Sources
- Bash Reference Manual - Looping Constructs
- Linux
seqCommand Manual - Advanced Bash-Scripting Guide - For Loops
- Stack Overflow - Bash variable ranges
- 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
seqcommand provides good readability and works well when you need sequences for other commands - Avoid
evalunless 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.