Can I use variables inside {} expansion without appealing to eval? If so, how?

This doesn't work:

$ touch 1.foo 1.bar
$ ls 1.{foo,bar}
1.bar  1.foo
$ extensions=foo,bar
$ ls 1.{$extensions}
ls: cannot access 1.{foo,bar}: No such file or directory

It works with eval:

$ eval ls 1.{$extensions}
1.bar  1.foo
  • 3
    variable expansion happens after word expansion, so {$...} will be a single word. 'tho {$foo,$bar} would work.
    – Ricky
    Commented Feb 26, 2014 at 23:07

3 Answers 3


Brace expansion happens very early during expansion (first thing, in fact), before variable expansion. To perform brace expansion on the result of a variable expansion, you need to use eval.

You can achieve the same effect without eval if you make extensions a wildcard pattern instead of a brace pattern. Set the extglob option to activate ksh-like patterns.

shopt -s extglob
ls 1.$extensions

Here is a way to expand variables inside braces without eval:

declare -a 'range=({'"1..$end"'})'

We now have a nice array of numbers:

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

Tested in bash 4.3.11 but should work in all modern versions.

  • just a piece of syntax I used: read line; declare -a "l=($line)"; echo ${l[@]} — I placed this to let a user input brace-expanded strings and view the result (in a kind of bash tutorial).
    – gluk47
    Commented Feb 29, 2016 at 19:34
  • 2
    In effect, that's like eval, it's not less dangerous than eval. With end='$(reboot)', reboot is run just the same. Commented Aug 23, 2017 at 9:14

Another technique you can use in bash and some other shells is to use an array:

$ extensions=(foo bar)
$ ls "${extensions[@]/#/1.}"

This is the ${parameter/pattern/string} (pattern substitution) form of parameter expansion.  (Unfortunately, this is not POSIX-compliant either.)  The [@] specifies that the substitution should be applied to each element of the array, and that the separation between the elements should be maintained.  # in a pattern string acts like a ^ in a regular expression (i.e., most s/old/new/ substitutions); it means that the substitution should occur only at the beginning of the parameter values.  So

$ ls "${extensions[@]/#/1.}"

is equivalent to

$ ls "${extensions[0]/#/1.}" "${extensions[1]/#/1.}"

(since this array has two elements, indexed as 0 and 1) and this expands to

$ ls "1.foo" "1.bar"


The quotes are important if the “extensions” require quotes — i.e., if they (might possibly, ever) contain spaces or pathname expansion (glob/wildcard) characters.  For example, if

$ extensions=("foo bar" "*r")


$ ls ${extensions[@]/#/1.}

(without quotes) would expand to

$ ls 1.foo bar 1.*r

(in which 1.foo and bar are separate arguments), and this, in turn, expands to

$ ls 1.foo bar 1.anteater 1.bar 1.bear 1.cougar 1.deer 1.grasshopper …

(because 1.*r is an unquoted wildcard).  For more discussion of the importance of quoting, see Security implications of forgetting to quote a variable in bash/POSIX shells.

You can also match the end of a string by using % in the pattern:

$ fnames=(cat dog)
$ ls "${fnames[@]/%/.c}"

expands to

$ ls "cat.c" "dog.c"

But beware: % doesn’t work the same as $ in regular expressions.  You have to put it at the beginning of the pattern to constrain the match to occur at the end of the parameter value.  For example, if you have

$ fnames=(cat.c dog.c)

and you want to get cat.o and dog.o, don’t do "${fnames[@]/.c%/.o}" — it won’t work.  Do

$ ls "${fnames[@]/%.c/.o}"

But don’t do "${fnames[@]/.c/.o}" (leaving out the % altogether) either — if one of the “fnames” is dog.catcher.c, it will be converted to dog.oatcher.c (since the first .c gets replaced with .o).

Unfortunately, there is no easy way to add a prefix and a suffix, as you would be able to with

$ ls 1.{foo,bar}.c

See the bash documentation for more information on variables, arrays, and the transformations you can apply to them.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .