12

In a comment on another post, @JonathanLeffler stated that:

{ ... } | somecommand is run in a sub-shell and doesn't affect the parent shell. Demo:

X=PQR; echo $X; { X=ABC; echo $X; } | cat; echo $X

(with output PQR, ABC, PQR on three lines)

and indeed:

james@bodacious-wired:tmp$X=PQR; echo $X; { X=ABC; echo $X; } | cat; echo $X
PQR
ABC
PQR

However, man bash says that { .. } does not execute in a subshell:

   { list; }
          list  is  simply executed in the current shell environment.  list must be 
          terminated with a newline or semicolon.  This is known as a group command. 

So what's going on here? Is man bash wrong? I know that each part of a pipeline executes in a subshell; but I don't see how that causes the observed behaviour. For instance:

james@bodacious-wired:tmp$X=PQR; echo $X | sed;  X=ABC; echo $X | sed; echo $X
PQR
ABC
ABC

Edited to add:

A few people have suggested using echo $$ to show that things are (or are not) parts of a subshell. This is not at all useful, as the $$ gets expanded during the parameter expansion stage, which happens long before any commands are executed.

As an example:

james@bodacious-wired:tmp$echo 1$$; ps; ( echo 2$$; ps ); echo 3$$; ps
11194
  PID TTY           TIME CMD
 1194 ttys000    0:00.22 -bash
21194
  PID TTY           TIME CMD
 1194 ttys000    0:00.22 -bash
 7894 ttys000    0:00.00 -bash
31194
  PID TTY           TIME CMD
 1194 ttys000    0:00.22 -bash
james@bodacious-wired:tmp$

You can see that the second invocation of ps happened inside a subshell, with pid 7894; but echo 2$$ still shows the value that bash substituted in the variable expansion phase, before it spawned the subshell

For contrast, and demonstrating that { .. } does not spawn a subshell:

james@bodacious-wired:tmp$echo 1$$; ps; { echo 2$$; ps; }; echo 3$$; ps
11194
  PID TTY           TIME CMD
 1194 ttys000    0:00.22 -bash
21194
  PID TTY           TIME CMD
 1194 ttys000    0:00.22 -bash
31194
  PID TTY           TIME CMD
 1194 ttys000    0:00.23 -bash

Just to prove that @nos is correct, add a pipeline to the above:

james@bodacious-wired:tmp$echo 1$$; ps; { echo 2$$; ps; } | sed ; echo 3$$; ps
11194
  PID TTY           TIME CMD
 1194 ttys000    0:00.25 -bash
21194
  PID TTY           TIME CMD
 1194 ttys000    0:00.25 -bash
 7945 ttys000    0:00.00 -bash
 7946 ttys000    0:00.00 sed
31194
  PID TTY           TIME CMD
 1194 ttys000    0:00.25 -bash

As expected, the shell spawns two subshells, one for each side of the pipeline.

John Kugelman
  • 349,597
  • 67
  • 533
  • 578
James Polley
  • 7,977
  • 2
  • 29
  • 33

2 Answers2

9

Each side of a pipeline becomes a subshell at least.

X=PQR; echo $X; { X=ABC; echo $X; } | cat; echo $X

will make a subshell/process of , atleast { X=ABC; echo $X; } and cat.

"Each command in a pipeline is executed as a separate process (i.e., in a subshell)." , from man bash

If you instead do this

X=PQR; echo $X; { X=ABC; echo $X; } ; echo | cat; echo $X

You'll see afterwards that echo $X shows ABC.

There's other ways that commands are executed in subshells too, e.g. if you background a subcommand: { X=SUB ; sleep 1; } & , that group will run in a subshell, whereas just { X=SUB ; sleep 1; } will not.

If you want to group commands that always execute in a subshell, use parenthesis, (X=ABC ; echo $X) instead of braces.

John Kugelman
  • 349,597
  • 67
  • 533
  • 578
nos
  • 223,662
  • 58
  • 417
  • 506
1

Indeed, the braces are executed in a new subshell, but only if piped. The first command is with cat, the second without:

xxx@yyy:~$ ps; X=PQR; echo $X; { ps; X=ABC; echo $X; } | cat; ps; echo $X
  PID TTY          TIME CMD
 6768 pts/7    00:00:00 bash
13158 pts/7    00:00:00 ps
PQR
  PID TTY          TIME CMD
 6768 pts/7    00:00:00 bash
13159 pts/7    00:00:00 bash
13160 pts/7    00:00:00 cat
13161 pts/7    00:00:00 ps
ABC
  PID TTY          TIME CMD
 6768 pts/7    00:00:00 bash
13162 pts/7    00:00:00 ps
PQR
xxx@yyy:~$ ps; X=PQR; echo $X; { ps; X=ABC; echo $X; }; ps; echo $X
  PID TTY          TIME CMD
 6768 pts/7    00:00:00 bash
13239 pts/7    00:00:00 ps
PQR
  PID TTY          TIME CMD
 6768 pts/7    00:00:00 bash
13240 pts/7    00:00:00 ps
ABC
  PID TTY          TIME CMD
 6768 pts/7    00:00:00 bash
13245 pts/7    00:00:00 ps
ABC

In this case 6768 is the PID of the terminal shell, and 13159 is the PID of the subshell started for the braces.

It seems that {} are executed in a subshell if (and only if) piped.

krlmlr
  • 25,056
  • 14
  • 120
  • 217
  • 3
    The pipe creates the subshell, not the braces. – James Polley Feb 02 '12 at 09:40
  • 1
    No, it's not the pipe, it's the braces in connection with the pipe. Check the output of `ps` vs. `ps | cat` vs. `{ ps; } | cat`.. – krlmlr Feb 02 '12 at 12:59
  • 2
    Braces never invoke a subshell! Only pipes will do this: http://unix.stackexchange.com/questions/127334/bash-subshell-creation-with-curly-braces – slm May 03 '14 at 04:28