tags:

views:

9684

answers:

6

How do I iterate over a range of numbers in bash when the range is given by a variable?

I know I can do this:

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

Which gives:

1
2
3
4
5

Yet how can I replace either of the range endpoints with a variable? This doesn't work:

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

Which prints:

{1..5}

+1  A: 

My version of bash doesn't seem to support the curly brace notation at all.

Can you do this?

for i in `echo {1..$END}`; do echo $i; done

Update: I found a bash that supports the curly braces; the above does not work.

Greg Hewgill
Really? What does 'bash --version' give you?
paxdiablo
$ bash --versionGNU bash, version 2.05b.0(1)-release (powerpc-apple-darwin8.0)Copyright (C) 2002 Free Software Foundation, Inc.
Greg Hewgill
Doesn't work in 3.0 or 3.2.
Zathrus
+4  A: 

You can use

for i in $(seq $END); do echo $i; done
Peter Hoffmann
seq involves the execution of an external command which usually slows things down.
paxdiablo
It doesn't involve the execution of an external command for each iteration, just once. If the time to launch one external command is an issue, you're using the wrong language.
Mark Baker
+1 for `$()` instead of backticks
Dennis Williamson
+16  A: 
for i in `seq 1 $END`; do echo $i; done
Jiaaro
seq involves the execution of an external command which usually slows things down. This may not matter but it becomes important if you're writing a script to handle lots of data.
paxdiablo
Just fine for a one-liner. Pax's solution is fine too, but if performance were really a concern I wouldn't be using a shell script.
eschercycle
Good point. I've found myself building a quick'n'dirty ksh script which ended up turning into a monster and I've not wanted to rewrite it given the time I've already spent - but that's probably just me.
paxdiablo
seq is good, but is specific to GNU (it is not in POSIX, nor available as standard on systems such as Solaris, HP-UX, AIX). I have my own program, range, which is equivalent to seq - I often wondered why other people didn't need similar functionality people. Darn useful for generating test data.
Jonathan Leffler
seq is called just once to generate the numbers. exec()'ing it shouldn't be significant unless this loop is inside another tight loop.
Javier
The external command isn't really relevent: if you're worried about the overhead of running external commands you don't want to be using shell scripts at all, but generally on unix the overhead is low.However, there is the issue of memory usage if END is high.
Mark Baker
A: 

This works for me in bash:

END=5
i=1 ; while [[ $i -le $END ]] ; do
    echo $i
    ((i = i + 1))
done
paxdiablo
`((i++))` works
Dennis Williamson
+11  A: 

discussion

Using seq is fine, as Jim Robert suggested. Pax Diablo suggested a bash loop to avoid calling a subprocess, with the additional advantage of being more memory friendly if $END is too large. Zathrus spotted a typical bug in the loop implementation, and also hinted that since i is a text variable, continuous conversions to-and-fro numbers are performed with an associated slow-down.

integer arithmetic

This is an improved version of the bash loop:

typeset -i i END
let END=5 i=1
while ((i<=END)); do
    echo $i
    …
    let i++
done

If the only thing that we want is the echo, then we could write echo $((i++)).

ephemient taught me something: bash allows for ((expr;expr;expr)) constructs. Since I've never read the whole man page for bash (like I've done with the ksh man page, and that was a long time ago), I missed that.

So,

typeset -i i END # let's be explicit
for ((i=1;i<=END;++i)); do echo $i; done

seems to be the cleanest way, and possibly the "fastest"; it sure won't be necessary to allocate memory to consume seq's output, which could be a problem if END is very large.

the initial question

eschercycle noted that the {a..b} bash notation works only with literals; true, accordingly to the bash manual. One can overcome this obstacle with a single (internal) fork() without an exec() (as is the case with calling seq, which being another image requires a fork+exec):

for i in $(eval echo "{1..$END}"); do

Both eval and echo are bash builtins, but a fork() is required for the command substitution (the $(…) construct).

ΤΖΩΤΖΙΟΥ
+6  A: 

The seq method is the simplest, but Bash has built-in arithmetic evaluation.

END=5
for ((i=1;i<=END;i++)); do
    echo $i
done
# ==> outputs 1 2 3 4 5 on separate lines

The "for ((expr1;expr2;expr2))" construct works just like "for (expr1;expr2;expr3)" in C and similar languages, and like other ((expr)) cases, Bash treats them as arithmetic.

ephemient
One constantly learns. I would prepend a `typeset -i i END` though. In the pre-bash times (i.e. ksh) it made a difference, but computers were much slower then.
ΤΖΩΤΖΙΟΥ