Search code examples
bashcommand-precedence

Order of brace expansion and parameter expansion


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.


Solution

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