7

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 Beam Feb 26 '14 at 23:07
9

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
extensions='@(foo|bar)'
ls 1.$extensions
3

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

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

We now have a nice array of numbers:

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

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 Feb 29 '16 at 19:34
  • 1
    In effect, that's like eval, it's not less dangerous than eval. With end='$(reboot)', reboot is run just the same. – Stéphane Chazelas Aug 23 '17 at 9:14
2

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"

Quoting

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")

then

$ 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.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.