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?

  • 1
    Excellent question, +1. It seems like it might be that { is interpreted as a command list if it appears at the beginning of a command and as a brace expansion otherwise, but I'm not sure. – Celada Jan 10 '16 at 21:57
  • 16
    {ls;echo hi} isn't legal bash. You need a space after the opening brace and a semicolon before the closing one. – PSkocik Jan 10 '16 at 22:09

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

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

$ echo {ls;echo hi}


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; }

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
  • 2
    this is really the best answer, because you give us indeed how bash parser works ! and with an detail explanation ! – lovespring Jan 11 '16 at 9:58
  • 2
    You don't need the space between ; and }. { ls;} works as the semicolon is already a meta-character. – choroba Jan 11 '16 at 12:03
  • 1
    @lovespring Thanks, yes I invested some time in writting it. I am happy to know that it is useful. Again, thanks. – user79743 Jan 11 '16 at 22:55
  • excellent article, thanks a lot for references – Edward Torvalds Jan 28 '16 at 9:43

The block { is a shell keyword, so it must separated from the next word by space, while in brace expansion, there should be no space (if you need to brace expand a space, you have to escape it: echo {\ ,a}{b,c}).

You can use brace expansion at the start of a command:

{ls,.}  # expands to "ls ."

You can't use it to expand to a block, though, as parsing of the grouping commands happens before expansions:

echo {'{ ls','.;}'}  # { ls .;}
{'{ ls','.;}'}       # bash: { ls: No such file or directory

It knows by checking the syntax of the command line. In the same way it knows that in the expression echo echo, the first echo should be treated as a command and the second echo as a parameter of the first echo.

In bash it is very simple, since { cmd; } should have spaces and semicolon. However, for example in zsh they are not needed, but still by analyzing context of {} shell is able to tell what should be done with its content.

Consider the following:

alias 1..3=date
{ 1..3; }    #in bash
{1..3}       #in zsh

Both return current date, but

echo {1..3}

returns 1 2 3 because shell knows {} in an argument for command echo, so should be expanded.

  • { followed by unquoted space doesn't start brace expansion in bash. – choroba Jan 10 '16 at 23:09
  • @choroba Yes, and not only right after {. Unquoted space cannot be anywhere because shell splits entire command line on spaces. – jimmij Jan 10 '16 at 23:42

Firstly, a the compound brace must be a word by itself and the first word of the command line:

echo { these braces are just words }

Secondly, individual braces are not special (as you can see above). Empty braces are also not special:

echo {} # just the token {}: familiar from the find command

Anything without commas is also just itself

echo {abc} # just {abc}

Here is where the action starts.

echo {a,b} # braces disappear, a b results.

So basically for brace expansion to kick in, we need a single word (not separated into fields on whitespace), inside of which there occurs at least one instance of {...} inside of which there occurs at least one comma.

This can be the first word in the command line, by the way:

{ls,-l} .   # just "ls -l ."

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.