Categories
bash for-loop shell syntax

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

1991

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}

9

  • 37

    Hi all, the informations and hints I have read here are all really helpful. I think it is best to avoid the use of seq. The reason is that some scripts need to be portable and must run on a wide variety of unix systems, where some commands may not be present. Just to do an example, seq is not present by default on FreeBSD systems.

    – user557212

    Jan 14, 2011 at 11:19

  • 1

  • 12

    I don’t remember since which version of Bash exactly but this command supports trailing zeros as well. Which sometimes is really helpful. Command for i in {01..10}; do echo $i; done would give numbers like 01, 02, 03, ..., 10.

    – topr

    Jun 15, 2016 at 9:33

  • 2

    For those like me who just want to iterate over the range of indices of an array, the bash way would be: myarray=('a' 'b' 'c'); for i in ${!myarray[@]}; do echo $i; done (note the exclamation mark). It’s more specific than the original question, but could help. See bash parameter expansions

    Feb 21, 2018 at 14:14

  • 1

    Brace expansion is also used for expressions like {jpg,png,gif} which isn’t directly addressed here, though the answer will be identical. See Brace expansion with variable? [duplicate] which is marked as a duplicate of this one.

    – tripleee

    Aug 20, 2018 at 5:46


2243

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

edit: I prefer seq over the other methods because I can actually remember it 😉

10

  • 54

    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

    Oct 4, 2008 at 1:45

  • 50

    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.

    Oct 4, 2008 at 1:49

  • 21

    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

    Oct 4, 2008 at 4:40

  • 32

    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.

    Oct 6, 2008 at 12:53

  • 24

    Note that seq $END would suffice, as the default is to start from 1. From man seq: “If FIRST or INCREMENT is omitted, it defaults to 1”.

    – fedorqui

    Aug 5, 2014 at 9:06

650

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;expr3)); construct works just like for (expr1;expr2;expr3) in C and similar languages, and like other ((expr)) cases, Bash treats them as arithmetic.

9

  • 98

    This way avoids the memory overhead of a large list, and a dependency on seq. Use it!

    – bobbogo

    Mar 14, 2011 at 20:08

  • 5

    @MarinSagovac Make sure to make #!/bin/bash the first line of your script. wiki.ubuntu.com/…

    – Melebius

    Apr 24, 2017 at 13:22

  • 13

    just a very short question on that: why ((i=1;i<=END;i++)) AND NOT ((i=1;i<=$END;i++)); why no $ before END?

    – Baedsch

    Sep 20, 2018 at 10:26

  • 14

    @Baedsch: for the same reason i is not used as $i. bash man page states for arithmetic evaluation: “Within an expression, shell variables may also be referenced by name without using the parameter expansion syntax.”

    Dec 6, 2018 at 11:59

  • 4

    I’ve included this answer in my performance comparison answer below. stackoverflow.com/a/54770805/117471 (This is a note to myself to keep track of which ones I have left to do.) I created that answer specifically to address @bobbogo’s unqualified claim and those that upvoted it. SPOILER: Memory overhead of a large list is not nearly as bad as the slow performance of c-style loops in bash. Comment there if you have thoughts. Let’s not hijack this thread.

    Feb 19, 2019 at 17:05

212

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 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 Korn shell (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 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 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

  • 1

    The only drawback to the C style loop that it cannot use command line arguments, as they begin with “$”.

    – karatedog

    Jan 9, 2012 at 13:15

  • 4

    @karatedog: for ((i=$1;i<=$2;++i)); do echo $i; done in a script works fine for me on bash v.4.1.9, so I don’t see a problem with command line arguments. Do you mean something else?

    – tzot

    Jan 9, 2012 at 14:41

  • It seems that eval solution is faster than built in C-like for: $ time for ((i=1;i<=100000;++i)); do :; done real 0m21.220s user 0m19.763s sys 0m1.203s $ time for i in $(eval echo “{1..100000}”); do :; done; real 0m13.881s user 0m13.536s sys 0m0.152s

    Jul 4, 2012 at 14:55


  • 3

    Yes, but eval is evil… @MarcinZaluski time for i in $(seq 100000); do :; done is a lot quicker!

    Aug 2, 2013 at 4:58

  • The performance must be platform specific since the eval version is quickest on my machine.

    Apr 2, 2014 at 23:25