Now follows the process hierarchy for a process pipeline between curly braces executed directly on command line. 4398 is the PID for the login shell. It's similar to the hierarchy above proving that everything is executed in current shell context:

Interesting, I would guess it's because in the 3rd case the inner group is part of pipeline, and hence it's executed in sub-shell e.g., as any other function call which is part of pipeline would be. Does it make sense?
– Miroslav KoškárApr 30 '14 at 18:30

2

I would not say "The shell must" just because it does... Pipelines are not executed in the shell context. If the pipeline consists of nothing but external commands then creating subprocesses is enough. { sleep 2 | command ps -H; }
– Hauke LagingApr 30 '14 at 18:37

3 Answers
3

In a pipeline, all commands run concurrently (with their stdout/stdin connected by pipes) so in different processes.

In

cmd1 | cmd2 | cmd3

All three commands run in different processes, so at least two of them have to run in a child process. Some shells run one of them in the current shell process (if builtin like read or if the pipeline is the last command of the script), but bash runs them all in their own separate process (except with the lastpipe option in recent bash versions and under some specific conditions).

{...} groups commands. If that group is part of a pipeline, it has to run in a separate process just like a simple command.

In:

{ a; b "$?"; } | c

We need a shell to evaluate that a; b "$?" is a separate process, so we need a subshell. The shell could optimise by not forking for b since it's the last command to be run in that group. Some shells do it, but apparently not bash.

"All three commands run in different processes, so at least two of them have to run in a subshell." Why is a subshell necessary in this case ? Can the parent shell not spawn tree processes ? Or, When you say "have to run in a subshell", do you mean the shell will fork itself, and then exec for each cmd1 cmd2 and cmd3 ? If I execute this bash -c "sleep 112345 | cat | cat " I only see one bash created and then 3 children to it without any other interleaving sub bashes.
– Hakan BabaOct 28 '17 at 7:40

"We need a shell to evaluate that a; b "$?" is a separate process, so we need a subshell." Could you also expand the reasoning ? Why we need a subsheel to understand that? What does it take to understand that? I presume parsing is required but what else ? . Can the parent shell not parse a; b "$?" ? Is there really a fundamental need for a subsheel, or maybe is it a design decision/ implementation on bash?
– Hakan BabaOct 28 '17 at 7:44

@HakanBaba, the parsing is done in the parent (the process that reads the code, the one that executed the interpreter unless that code was passed to eval), but the evaluation (run the first command, wait for it, run the second) is done in the child, the one that has stdout connected to the pipe.
– Stéphane ChazelasOct 28 '17 at 7:48

in { sleep 2 | ps -H; } the parent bash sees sleep 2 that requires a fork/exec. But in { { sleep 2; } | ps -H; } the parent bash sees { sleep 2; } in other words, some bash code. It looks like the parent can handle the fork/exec for sleep 2 but spawns a new bash recursively to handle the encountered bash code. That is my understanding, does it make sense ?
– Hakan BabaOct 28 '17 at 7:55

Nesting the curly braces would seem to denote that you're creating an additional level of scoping which requires a new sub-shell to be invoked. You can see this effect with the 2nd copy of Bash in your ps -H output.

Only the processes stipulated in the first level of curly braces are run within the scope of the original Bash shell. Any nested curly braces will run in their own scoped Bash shell.

@slm it's just the emphasis on the main point of the answer, as I saw it. Commands in the first level of curly braces run in current shell, nested curly braces create new shells. Parentheses differ in creating subshell right away, at the first level. If I got it wrong -- correct me. But, as others point, the initial question has also pipelines. Thus the creation of separate processes. And curly braces have to create a shell when used for a separate process. So, probably, it is the reason for the behaviour in question.
– xealitsDec 1 '14 at 17:12

I'll post results of my tests, which leads me to conclusion that bash makes a sub-shell for a group command if and only if it's a part of pipeline, that is similar as if one would call some function which would be also called in sub-shell.