13

A bash script that contains

for i in {a,b}-{1,2}; do
  echo $i;
done

prints

a-1
a-2
b-1
b-2

when executed. This is what I expected - as the {a,b} construct is expanded.

However, when (another) script contains

v={a,b}-{1,2}
echo $v

it prints

{a,b}-{1,2}

which is not what I expected. I expected it to print a-1 a-2 b-1 b-2. Obviously, the {a,b} construct is not expanded.

I can make it expand like so

v=$(echo {a,b}-{1,2})

Based on these observations I have two questions: 1) when is the {a,b} construct expanded? 2) is $(echo {a,b}-{1,2}) the preferred way to trigger an expansion when required?

René Nyffenegger
  • 2,201
  • 2
  • 23
  • 28
  • 1
    This is, at least, consistent with the fact that you cannot make an array variable with a simple `=`. For example, `v=a-1 a-2` will not work. – grochmal Jul 05 '16 at 20:05
  • @grochmal - that's because you're assigning a scalar value. `v=a-1 a-2` means `assign 'a-1' to variable v and run 'a-2'` `v=(a-1 a-2)` assigns the array to variable `v`. `v+=(b-1 b-2)` appends to it. – cas Jul 06 '16 at 10:44

4 Answers4

15

The Bash manual says that:

SIMPLE COMMAND EXPANSION
When a simple command is executed, the shell performs the following
expansions, assignments, and redirections, from left to right.
[...]
4. The  text  after the = in each variable assignment undergoes tilde
   expansion, parameter expansion, command substitution, arithmetic
   expansion, and quote removal before being assigned to the variable.

Brace expansion is not in the list, so it isn't performed for the assignment v={a,b}-{1,2}. As mentioned by @Wildcard, the simple expansion to v=a-1 v=b-1 ... would be senseless anyway.

Also, when executing the echo $v, the following applies:

EXPANSION
Expansion is performed on the command line after it has been split
into words. [...]

The order of expansions is: brace expansion; tilde expansion, 
parameter and variable expansion, arithmetic expansion, and command
substitution (done in a left-to-right fashion); word splitting; and
pathname expansion.

Brace expansion happens before variable expansion, so the braces assigned to $v aren't expanded.

But you can do stuff like this:

$ var_this=foo var_that=bar
$ echo $var_{this,that}
foo bar

Expanding it with $(echo ...) should work if you don't have any whitespace in the string to be expanded, and hence won't run into problems with word splitting. A better way might be to use an array variable if you can.

e.g. save the expansion into an array and run some command with the expanded values:

$ v=( {a,b}-{1,2} )
$ some_command "${v[@]}"
ilkkachu
  • 133,243
  • 15
  • 236
  • 397
5

An interesting point. Possibly helpful is the following excerpt from man bash:

A variable may be assigned to by a statement of the form

      name=[value]

If value is not given, the variable is assigned the null string.  All values undergo tilde expansion, parameter and variable expansion, command substitution, arithmetic expansion, and quote removal (see EXPANSION below).

Note that brace expansion is NOT mentioned in the list.

However this still leaves a question, namely: How does the shell know that this is a variable assignment and thus not subject to brace expansion? Or more precisely, where is the parsing sequence clarified and codified such that the shell is defined to identify variable assignments before it handles brace expansion? (This is obviously how bash works, but I haven't found the exact line of documentation describing this.)

Wildcard
  • 35,316
  • 26
  • 130
  • 258
  • 1
    Maybe [this](https://www.gnu.org/software/bash/manual/html_node/Simple-Command-Expansion.html#Simple-Command-Expansion)? "2. The words that are not variable assignments or redirections are expanded (see Shell Expansions)." – Jeff Schaller Jul 05 '16 at 20:32
  • @JeffSchaller, you're correct. Actually this question is best answered by just reading the section "SIMPLE COMMAND EXPANSION" (`LESS=+/SIMPLE man bash`). – Wildcard Jul 05 '16 at 20:43
0

according to my lil' knowledge the {a,b,c} is expanded when it's directly echoed or used with a command e.g: mkdir ~/{a,b,c}, but when it's set to a variable it should be evaluated before echoing it or using it as arggument!

u@h:~$ echo {a,b}-{1,2}
a-1 a-2 b-1 b-2
u@h:~$ v={a,b}-{1,2}
u@h:~$ echo $v
{a,b}-{1,2}
u@h:~$ eval echo $v
a-1 a-2 b-1 b-2

since you have "a" followed by "b" in alpha order [a-z], and "1" followed by "2" in dec order [0-9] : you can use double period ".." insteed of comma ","

u@h:~$ echo {a..b}-{1..2}
a-1 a-2 b-1 b-2
u@h:~$ v={a..b}-{1..2}
u@h:~$ echo $v
{a..b}-{1..2}
u@h:~$ eval echo $v
a-1 a-2 b-1 b-2
Yunus
  • 1,634
  • 2
  • 13
  • 19
  • 1
    The question is why `v` wasn't *set* to the literal string "a-1 a-2 b-1 b-2". Or at least why no error was thrown as you get if you type the command: `v=a-1 v=a-2 v=b-1 v=b-2` (which you would expect that variable assignment to be expanded to). It's a good question; this doesn't really answer it. – Wildcard Jul 05 '16 at 20:16
  • @SatoKatsura, what is "wildcard expansion" supposed to mean? There is parameter expansion, pathname expansion, and brace expansion—any of which you could be referring to, and all of which are different. Or did you mean word splitting? – Wildcard Jul 05 '16 at 20:24
0

Assigning to a variable in bash does not expand the expression.

For this little script, x will contain "*" and not the expansion of "*":

#!/bin/bash
x=*
echo "$x"

However, some values are expanded, ref. the nice answer from ilkkachu.

Expressions are expanded when they are evaluated.

Like this:

x=$(echo *)        # <-- evaluation of "*"
echo "$x"

Or like this:

x=*
echo $x            # <-- evaluation of $x

Or like this:

x=*
eval echo "$x"     # <-- evaluation of `echo *`

Triggering $() is fairly common and I think it is preferred over eval, but the best is probably not to trigger evaluation at all, until the variable is actually used in a command.

Alexander
  • 9,607
  • 3
  • 40
  • 59