95

How does one use a variable in a bash for loop? If I just use a standard for loop, it does what I expect

for i in {0..3}
do
   echo "do some stuff $i"
done

This works fine. It loops thru 4 times, 0 to 3 inclusive, printing my message and putting the count at the end.

do some stuff 0
do some stuff 1
do some stuff 2
do some stuff 3

When I try the same thing with the following for loop, it seems to equal a string, which is not what i want.

length=3
for i in {0..$length}
do
   echo "do something right $i"
done

output:

do something right {0..3}

I've tried

for i in {0.."$length"} and for i in {0..${length}} (both output was {0..3})

and

for i in {0..'$length'} (output was {0..$length})

and they both don't do what I need. Hopefully someone can help me. Thanks in advance for any bash expert's help with for loops.

Classified
  • 5,759
  • 18
  • 68
  • 99

3 Answers3

161

One way is using eval:

for i in $( eval echo {0..$length} )
do
       echo "do something right $i"
done

Note what happens when you set length=;ls or length=; rm * (don't try the latter though).

safely, using seq:

for i in $( seq 0 $length )
do
       echo "do something right $i"
done

or you can use the c-style for loop, which is also safe:

for (( i = 0; i <= $length; i++ )) 
do 
       echo "do something right $i"
done
perreal
  • 94,503
  • 21
  • 155
  • 181
20

In bash, brace expansion is the first step attempted so, at that point, $length will not have been substituted.

The manpage for bash states clearly:

A sequence expression takes the form {x..y[..incr]}, where x and y are either integers or single characters ...

There are a number of possibilities, such as using:

pax> for i in $(seq 0 $length) ; do echo $i ; done
0
1
2
3

though that may give you a large command line if length is massive.

Another alternative is to use the C-like syntax:

pax> for (( i = 0; i <= $length; i++ )) ; do echo $i; done
0
1
2
3

It's also possible to omit $ sign in double parentheses to refer a variable:

ubuntu@ip-172-31-28-53:~/playground$ length=3;
ubuntu@ip-172-31-28-53:~/playground$ for ((i=0;i<=length;i++));do echo $i;done
0
1
2
3
jumping_monkey
  • 5,941
  • 2
  • 43
  • 58
paxdiablo
  • 854,327
  • 234
  • 1,573
  • 1,953
9

Brace subtitutions are performed before any other, so you need to use eval or a third-party tool like seq.

Example for eval:

for i in `eval echo {0..$length}`; do echo $i; done

This information can actually be found in man bash:

A sequence expression takes the form {x..y[..incr]}, where x and y are either integers or single characters, and incr, an optional increment, is an integer. [...]

Brace expansion is performed before any other expansions, and any characters special to other expansions are preserved in the result. It is strictly textual. Bash does not apply any syntactic interpretation to the context of the expansion or the text between the braces.

nemo
  • 55,207
  • 13
  • 135
  • 135