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 (called "sequence expression" in the Bash documentation):
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}
This works in Bash and Korn, also can go from higher to lower numbers. Probably not fastest or prettiest but works well enough. Handles negatives too.
The POSIX way
If you care about portability, use the example from the POSIX standard:
Output:
Things which are not POSIX:
(( ))
without dollar, although it is a common extension as mentioned by POSIX itself.[[
.[
is enough here. See also: What is the difference between single and double square brackets in Bash?for ((;;))
seq
(GNU Coreutils){start..end}
, and that cannot work with variables as mentioned by the Bash manual.let i=i+1
: POSIX 7 2. Shell Command Language does not contain the wordlet
, and it fails onbash --posix
4.3.42the dollar at
i=$i+1
might be required, but I'm not sure. POSIX 7 2.6.4 Arithmetic Expansion says:but reading it literally that does not imply that
$((x+1))
expands sincex+1
is not a variable.The
seq
method is the simplest, but Bash has built-in arithmetic evaluation.The
for ((expr1;expr2;expr3));
construct works just likefor (expr1;expr2;expr3)
in C and similar languages, and like other((expr))
cases, Bash treats them as arithmetic.If you're on BSD / OS X you can use jot instead of seq:
This is another way:
discussion
Using
seq
is fine, as Jiaaro 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 sincei
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:
If the only thing that we want is the
echo
, then we could writeecho $((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 Korn shell (ksh
) man page, and that was a long time ago), I missed that.So,
seems to be the most memory-efficient way (it won't be necessary to allocate memory to consume
seq
's output, which could be a problem if END is very large), although probably not the “fastest”.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 anexec()
(as is the case with callingseq
, which being another image requires a fork+exec):Both
eval
andecho
are Bash builtins, but afork()
is required for the command substitution (the$(…)
construct).