Bash – How Does Bash Differentiate Between Brace Expansion and Command Grouping?

bashshell-script

I've noticed that { can be used in brace expansion:

echo {1..8}

or in command grouping:

{ls;echo hi}

How does bash know the difference?

Best Answer

A simplified reason is the existence of one character: space.

Brace expansions do not process (un-quoted) spaces.

A {...} list needs (un-quoted) spaces.

The more detailed answer is how the shell parses a command line.


The first step to parse (understand) a command line is to divide it into parts.
These parts (usually called words or tokens) result from dividing a command line at each meta-character from the link:

  1. Splits the command into tokens that are separated by the fixed set of meta-characters: SPACE, TAB, NEWLINE, ;, (, ), <, >, |, and &. Types of tokens include words, keywords, I/O redirectors, and semicolons.

Meta-characters: spacetabenter;,<>| and &.

After splitting, words may be of a type (as understood by the shell):

  • Command pre-asignements: LC=ALL ...
  • Command LC=ALL echo
  • Arguments LC=ALL echo "hello"
  • Redirection LC=ALL echo "hello" >&2

Brace expansion

Only if a "brace string" (without spaces or meta-characters) is a single word (as described above) and is not quoted, it is a candidate for "Brace expansion". More checks are performed on the internal structure later.

Thus, this: {ls,-l} qualifies as "Brace expansion" to become ls -l, either as first word or argument (in bash, zsh is different).

$ {ls,-l}            ### executes `ls -l`
$ echo {ls,-l}       ### prints `ls -l`

But this will not: {ls ,-l}. Bash will split on space and parse the line as two words: {ls and ,-l} which will trigger a command not found (the argument ,-l} is lost):

 $ {ls ,-l}
 bash: {ls: command not found

Your line: {ls;echo hi} will not become a "Brace expansion" because of the two meta-characters ; and space.

It will be broken into this three parts: {ls new command: echo hi}. Understand that the ; triggers the start of a new command. The command {ls will not be found, and the next command will print hi}:

$ {ls;echo hi}
bash: {ls: command not found
hi}

If it is placed after some other command, it will anyway start a new command after the ;:

$ echo {ls;echo hi}
{ls
hi}

List

One of the "compound commands" is a "Brace List" (my words): { list; }.
As you can see, it is defined with spaces and a closing ;.
The spaces and ; are needed because both { and } are "Reserved Words".

And therefore, to be recognized as words, must be surrounded by meta-characters (almost always: space).

As described in the point 2 of the linked page

  1. Checks the first token of each command to see if it is .... , {, or (, then the command is actually a compound command.

Your example: {ls;echo hi} is not a list.

It needs a closing ; and one space (at least) after {. The last } is defined by the closing ;.

This is a list { ls;echo hi; }. And this { ls;echo hi;} is also (less commonly used, but valid)(Thanks @choroba for the help).

$ { ls;echo hi; }
A-list-of-files
hi

But as argument (the shell knows the difference) to a command, it triggers an error:

$ echo { ls;echo hi; }
bash: syntax error near unexpected token `}'

But be careful in what you believe the shell is parsing:

$ echo { ls;echo hi;
{ ls
hi
Related Question