Unix & Linux Stack Exchange is a question and answer site for users of Linux, FreeBSD and other Un*x-like operating systems. Join them; it only takes a minute:

Sign up
Here's how it works:
  1. Anybody can ask a question
  2. Anybody can answer
  3. The best answers are voted up and rise to the top

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?

share|improve this question
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 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 at 22:09
up vote 35 down vote accepted

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
share|improve this answer
2  
this is really the best answer, because you give us indeed how bash parser works ! and with an detail explanation ! – lovespring Jan 11 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 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. – BinaryZebra Jan 11 at 22:55
    
excellent article, thanks a lot for references – edward torvalds Jan 28 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
share|improve this answer

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.

share|improve this answer
    
{ followed by unquoted space doesn't start brace expansion in bash. – choroba Jan 10 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 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 ."
share|improve this answer

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

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