0

Bash manual says that brace expansion is performed before any other expansions.

I am writing a script which accepts two arguments:

#! /bin/bash

for b in {$1..$2}; do echo $b; done

I run it like:

$ ./myscript 0002 0010
{0002..0010}

The output isn't what I hope. I hope to perform parameter expansion before brace expansion. The expected output of my example is 0002 0003 0004 0005 0006 0007 0008 0009 0010, not 2 3 4 5 6 7 8 9 10. What would you replace {$1..$2} with?

I hope that the solution works even when $1 and $2 are strings not just made of digits, but also made of letters and digits.

Note that the values $1 and $2 can only be given as arguments to the script. I think it is clear from the beginning, but point it out in case not.

Tim
  • 101,790

3 Answers3

2

To do what you ask for: perform parameter expansion before brace expansion.

We need to delay brace expansion, that is easily done by quoting it \{...\} and calling eval:

$ set -- 5 10
$ eval printf \'%s \' \{$1..$2\}
5 6 7 8 9 10

That would work fine as long as there are no commands inside $1 or $2.
That is a security risk of this solution. One way to mitigate the issue is to make sure that the variables only contain numbers:

#!/bin/bash

a=${1//[^0-9]/}          ### select only numbers from first parameter.
b=${2//[^0-9]/}          ### select only numbers from second parameter.

c=$(eval printf \'%s \' \{$a..$b\})

for i in $c; do echo "$i"; done

The above code is missing some quoting that this version corrects:

#!/bin/bash
a=${1//[^0-9]/}          ### select only numbers from first parameter.
b=${2//[^0-9]/}          ### select only numbers from second parameter.
c=( $(eval printf \'%s \' \{$a..$b\}) )

for i in "${c[@]}"; do echo "$i"; done

### Or just
# printf '%s\n' "${c[@]}"

But to print a list of numbers, an arithmetic for loop looks as a better solution:

#!/bin/bash

a=${1//[^0-9]/}          ### select only numbers from first parameter.
b=${2//[^0-9]/}          ### select only numbers from second parameter.

for (( i=$a; i<=$b; i++)); do
    printf '%0*d\n' 5 "$i"
done    

Use it as:

$ script.sh 5 15
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
1

Brace Expansion is not defined by POSIX, so perhaps you could ditch it altogether:

seq $*

Or:

j=$1
while [ $j -le $2 ]
do
  echo $j
  j=$((j+1))
done

Or:

echo $* | awk '{for (j = $1; j <= $2; j++) print j}'
Zombo
  • 1
  • 5
  • 44
  • 63
0
#! /bin/bash

seq -w $1 $2

This may be what you find!

user164825
  • 3,636