Bash: parse of $${x,y}

207 Views Asked by At

I found that Bash does not expand a brace after $$.

$ echo $BASH_VERSION
5.1.16(1)-release
$ echo $${x,y}
4821{x,y}      #not expanded (though $$ is evaluated.) ---> Why?!?!?!

How should I explain this behavior?

I have tried similar patterns as follows. I can understand them.

$ echo $?{x,y}
0x 0y          #{x,y} is expanded. 
$ echo ${$}{x,y} 
4821x 4821y    #expanded (We should write like this instead of $${x,y}.)
$ echo $PPID{x,y}
               #not expanded (maybe parsed as a variable "PPID{x,y}")
3

There are 3 best solutions below

3
Kaz On BEST ANSWER

Bash performs brace expansions before parameter expansions. Brace expansion does not do a full analysis of the syntax to be able to determine what is a brace expansion or not. It implements a simple rule the sequence ${ is not treated as brace expansion, even if it looks like it.

If ${a,b} were allowed to produce $a $b, it would mean that brace expansion has to analyze, in detail, every single instance of ${...} syntax, in order to correctly detect brace expansions, and leave alone parameter expansions.

Now, the expansion does kick in if we escape the dollar sign: \${a,b}. Then we get $a $b (which are not treated as variables). So the real rule is actually subtle: if ${ occurs where the dollar sign is unescaped, then the brace is not scanned for race expansion.

This logic is not perfect, and making it perfect is fraught with pitfalls.

You cannot simply allow $${ because this could be part of a larger syntax which looks like $$${, where the first two $$ are the PID, followed by ${.

Quick, tell me, should this undergo brace expansion?

$$\$$$$$$$$$\$\$$$\$$$$$$$\$$$$$${a,b}

Trying to implement complex exceptions to the ${ rule would be a fool's errand. The correct solution would be for brace expansion to earnestly parse the syntax so that it accurately knows what is a braced parameter expansion and what isn't.

The workaround for this and related issues is to distribute one or more characters into your brace expansion.

That is to say:

$${a,b} -> {$$a,$$b}

Another case: say we have $a and $b variables that we want to expand, and we write ${a,b}. That's not supported, and thus, we manually distribute the dollar sign into the brace expansion:

${a,b} -> {$a,$b}

In other cases we don't have to. If we have two variables $foobar and $foobaz, then the brace expansion $fooba{r,z} will produce $foobar and $foobaz that then expand. When there is nothing between the $ and the {, we need the workaround of distributing the $ sign.

For $$, other workarounds are possible:

"$$"{a,b}  # $$ is expanded inside double quotes, like all params
${$}{a,b}  # The second $ can be braced, like a param name.
1
jhnc On

Although it looks like a bug, this behaviour appears to be deliberate.

The manpage says (emphases mine):

Brace expansion is performed before any other expansions, and any characters special to other expansions are preserved in the result. It is strictly textual. Bash does not apply any syntactic interpretation to the context of the expansion or the text between the braces.

A correctly-formed brace expansion must contain unquoted opening and closing braces, and at least one unquoted comma or a valid sequence expression. Any incorrectly formed brace expansion is left unchanged. A { or , may be quoted with a backslash to prevent its being considered part of a brace expression. To avoid conflicts with parameter expansion, the string ${ is not considered eligible for brace expansion, and inhibits brace expansion until the closing }.


As noted in the comments, the workaround is to use ${$} instead of $$.

2
Philippe On

According to the source :

#if defined (SHELL)
      /* If compiling for the shell, treat ${...} like \{...} */
      if (c == '$' && text[i+1] == '{' && quoted != '\'')       /* } */
    {
      pass_next = 1;
      i++;
      if (quoted == 0)
        level++;
      continue;
    }
#endif

https://github.com/bminor/bash/blob/f3b6bd19457e260b65d11f2712ec3da56cef463f/braces.c#L639

An opening brace { following a dollar sign $ gets skipped despite the fact that there is another dollar signs before the preceding one.