Order of brace expansion and parameter expansion

Viewed 318

A common trope on StackOverflow is: "Why doesn't x=99; echo {1..$x} work?" The answer is "because braces are expanded before parameters/variables".

Therefore, I thought it should be possible to expand multiple variables using a single $ and a brace. I'd expect a=1; b=2; c=3; echo ${{a..c}} to print 1 2 3. First, the inner brace would expand to ${a} ${b} ${c} (which it does when writing echo \${{a..c}}). Then that result would undergo parameter expansion.
However, I got -bash: ${{a..c}}: bad substitution so {a..c} wasn't expanded at all.

Bash's manual is a bit more specific (emphasis mine).

Expansion is performed on the command line after it has been split into tokens [...] 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 filename expansion.

Note the ; and , in that list. "Left-to-right fashion" seems to apply to the whole (therefore unordered) list before the ;. Just like the mathematical operators * and / have no precedence over each other.

Ok, so brace expansion is not really of higher precedence than parameter expansion. It's just that both {1..$x} and ${{a..c}} are evaluated from left to right, meaning the brace { comes before the parameter $x and the parameter ${ comes before the brace {a..c}.

Or so I thought. However, when using $ instead of ${ then parameters on the left expand after braces on the right:

# in bash 5.0.3(1)
x=nil; x1=one; x2=two
echo ${x{1..2}} # prints `-bash: ${x{1..2}}: bad substitution`
echo $x{1..2}   # prints `one two`

Question

  • Could it be that the bash manual is flawed or did I read it wrong?
  • If the manual is flawed: What is the exact order of all expansions?

I'm just asking because I'm curious. I don't plan to use thinks like $x{1..2} anywhere. I'm not interested in better solutions or alternatives to address multiple variables (e.g. array slices ${array[@]:1:2}). I just want to get a deeper understanding.

2 Answers

from: https://www.gnu.org/software/bash/manual/html_node/Brace-Expansion.html

To avoid conflicts with parameter expansion, the string ‘${’ is not considered eligible for brace expansion, and inhibits brace expansion until the closing ‘}’.

That said, for echo $x{1..2} , first the brace expansion takes place, and then the parameter expansion, so we have echo $x1 $x2. For echo ${x{1..2}} the brace expansion doesn't happen, because we are after the ${ and haven't reached the closing } of the parameter expansion.

Regarding the bash manual part you have quoted, left-to-right order still exists for the expansions (with respect to allowed nested ones). Things get clearer if you format the list instead of using , and ;:

  1. brace expansion
  2. In a left-to-right fashion:
    tilde expansion, parameter and variable expansion, arithmetic expansion, and command substitution
  3. word splitting
  4. filename expansion.

Read Mo Budlong's 1988 classic Command Line Psychology, which was written for regular Unix, but most of it still applies to bash. The order of evaluation goes:

1 History substitution (except for the Bourne shell)
2 Splitting words, including special characters
3 Updating the history list (except for the Bourne shell)
4 Interpreting single and double quotes
5 Alias substitution (except for the Bourne shell)
6 Redirection of input and output (< > and |)
7 Variable substitution (variables starting with $)
8 Command substitution (commands inside back quotes)
9 File name expansion (file name wild cards) 

So what bash does with code like {1..3} happens before step 7 above, and that's why the OP code fails.

But if we must, there's always eval, (which should only be used if the variables are known in advance, or first cautiously type checked):

a=1; b=2; c=3; eval echo \{$a..$c}

Output:

1 2 3
Related