9

I am having a problem with builtin sequences (ie: not using seq) in Bash when the seq number is a variable. For example, this works and print me 1 2 3:

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

but this :

a=3
for i in {1..$a};do
   echo $i
done 

fail and print me {1..3} only

This works with ZSH and I know I have an alternative to make a counter thing but wondering if this is a bug or a brace expansion feature!

Tom Kelly
  • 1,458
  • 17
  • 25
Chmouel Boudjnah
  • 2,541
  • 3
  • 24
  • 28

7 Answers7

6

In Bash, brace expansion is performed before variable expansion. See Shell Expansions for the order.

$ a=7; echo {1..3} {4..$a}
1 2 3 {4..7}

If you want to use a variable, use C-style for loops as in Shawn's answer.

Dennis Williamson
  • 346,391
  • 90
  • 374
  • 439
5

An alternative would be to use the double-parenthesis construct which allows C-style loops:

A=3
for (( i=1; i<=$A; i++ )); do
    echo $i
done
Cyrus
  • 84,225
  • 14
  • 89
  • 153
Shawn Chin
  • 84,080
  • 19
  • 162
  • 191
4
$ num=3
$ for i in $( eval echo {1..$num});do echo $i;done
1
2
3
kurumi
  • 25,121
  • 5
  • 44
  • 52
  • look like kind of a hack to me... but thanks.. – Chmouel Boudjnah Feb 10 '11 at 11:41
  • 1
    Less efficient than a C-style loop (due to use of a subshell) and bears a security risk: do not use `eval` unless you fully control or have validated the value of the variables used in the string passed to `eval`. Otherwise, arbitrary commands may get executed. For instance, the following carefully crafted `$num` value will enumerate the files in the current user's home folder: `num='};ls ~;{'; for i in $(eval echo {1..$num});do echo $i;done` – mklement0 Jun 23 '16 at 03:19
3
    #!/bin/bash - see comment for list of obsolete bash constructs
    function f_over_range {
        for i in $(eval echo {$1..$2}); do
            f $i
        done
    }

    function f {
        echo $1
    }

    #POSIX-compliant
    f_over_range() {
        for i in $(eval echo {$1..$2}); do
            f $i
        done
    }

    f() {
        echo $1
    }


    f_over_range 0 5
    f_over_range 00 05

Notes:

  • Using eval exposes command injection security risk
  • Linux will print "00\n01\n02..etc", but OSX will print "0\n1\n2\n...etc"
  • Using seq or C-style for loops won't match brace expansion's handling of leading zeros
  • 1
    Might I suggest using POSIX-compliant function declaration syntax? See http://wiki.bash-hackers.org/scripting/obsolete -- `function f {` is a ksh-ism supported for legacy reasons. – Charles Duffy Aug 24 '18 at 19:35
  • The documentation linked is a bit ambiguous to me. The first table recommends a replacement `NAME() COMPOUND-CMD or function NAME { CMDS; }`, whereas the third table recommends the replacement `NAME() COMPOUND-CMD`. I've updated my answer based on [TLDP](http://tldp.org/LDP/abs/html/functions.html) and my interpretation. – hotfix_cowboy Aug 26 '18 at 01:42
  • Granted -- *mixing* POSIX and ksh syntax is worse than using ksh syntax, as it leads to code that isn't even compatible with ksh, and so such mixing is the thing the linked documentation is most strongly trying to warn someone off from. That said, the ABS is somewhat notorious as a source of bad-practice examples. – Charles Duffy Aug 26 '18 at 18:58
  • See http://wooledge.org/~greybot/meta/abs tracking the history of the ABS factoid in the freenode #bash channel (the first column is seconds-since-epoch timestamp for each change), and the "Books and Resources" section of https://stackoverflow.com/tags/bash/info, which as of this writing does not feature the ABS at all among its recommended sources. – Charles Duffy Aug 26 '18 at 19:02
1

Other option is to use seq command:

a=3; for i in $(seq 1 $a);do echo $i;done
user2153517
  • 725
  • 7
  • 9
0

I also needed to do somenthing like:

n=some number; {1..$n..increment}

so I used this workaround:

n=100  
i=1
while [ $i -lt $n ]
do
echo $i
i=$(( $i+1 ))
done
Gigiux
  • 144
  • 1
  • 7
-1

try this:

$ start=3
$ end=5
$ echo {$(echo $start)..$(echo $end)}
Duo Li
  • 21
  • 5
  • 1
    That prints the following _literal_: `{3..5}`, which is clearly not the intent. If you follow the link in the accepted answer, you'll see that brace expansion (`{...}`) happens _before_ command substitution (`$(...)`), which is why your approach cannot work (in _Bash_, as tagged; it _would_ work in Ksh and Zsh, but there you can use variables _directly_ in brace expansions anyway, so you'd just use `start=3 end=5; echo {$start..$end}`). – mklement0 Jun 23 '16 at 03:09
  • sorry, i made a mistake, echo {$(echo $start)..$(echo $end)} does not work, but {$start,$end} outputs "3 5". – Duo Li Nov 17 '16 at 03:17
  • Yes, it does, but that's the _list_ form of brace expansion (`,`-separated items), whereas what the OP is looking for is the _sequence_ (range) form (`..`). In concrete terms: it's not `3 5` that's the desired output, but `3 4 5`. – mklement0 Nov 17 '16 at 12:53