Take the 2-minute tour ×
Unix & Linux Stack Exchange is a question and answer site for users of Linux, FreeBSD and other Un*x-like operating systems.. It's 100% free, no registration required.

According to this, placing a list of commands between curly braces causes the list to be executed in the current shell context. No subshell is created.

Using ps to see this in action

This is the process hierarchy for a process pipeline executed directly on command line. 4398 is the PID for the login shell:

sleep 2 | ps -H;
  PID TTY          TIME CMD
   4398 pts/23   00:00:00 bash
   29696 pts/23   00:00:00   sleep
   29697 pts/23   00:00:00   ps

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:

{ sleep 2 | ps -H; }
   PID TTY          TIME CMD
    4398 pts/23   00:00:00 bash
    29588 pts/23   00:00:00   sleep
    29589 pts/23   00:00:00   ps

Now, this is the process hierarchy when the sleep in the pipeline is itself placed inside curly braces (so two levels of braces in all)

{ { sleep 2; } | ps -H; }
  PID TTY          TIME CMD
   4398 pts/23   00:00:00 bash
   29869 pts/23   00:00:00   bash
   29871 pts/23   00:00:00     sleep
   29870 pts/23   00:00:00   ps

Why does bash have to create a subshell to run sleep in the 3rd case when the documentation states that commands between curly braces are executed in current shell context?

share|improve this question
    
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 Koskar Apr 30 at 18:30
1  
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 Laging Apr 30 at 18:37

3 Answers 3

up vote 6 down vote accepted

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 subshell. Some shells run one of them in the current shell process, 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.

share|improve this answer

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.

Example

$ { { { sleep 2; } | sleep 2; } | ps -H; }
  PID TTY          TIME CMD
29190 pts/1    00:00:00 bash
 5012 pts/1    00:00:00   bash
 5014 pts/1    00:00:00     bash
 5016 pts/1    00:00:00       sleep
 5015 pts/1    00:00:00     sleep
 5013 pts/1    00:00:00   ps

Taking the | ps -H out of the mix just so we can see the nested curly braces, we can run ps auxf | less in another shell.

saml     29190  0.0  0.0 117056  3004 pts/1    Ss   13:39   0:00  \_ bash
saml      5191  0.0  0.0 117056  2336 pts/1    S+   14:42   0:00  |   \_ bash
saml      5193  0.0  0.0 107892   512 pts/1    S+   14:42   0:00  |   |   \_ sleep 20
saml      5192  0.0  0.0 107892   508 pts/1    S+   14:42   0:00  |   \_ sleep 20
saml      5068  0.2  0.0 116824  3416 pts/6    Ss   14:42   0:00  \_ bash
saml      5195  0.0  0.0 115020  1272 pts/6    R+   14:42   0:00      \_ ps auxf
saml      5196  0.0  0.0 110244   880 pts/6    S+   14:42   0:00      \_ less

But wait there's more!

If you take out the pipes though and use this form of a command we see what you'd actually expect:

$ { { { sleep 10; } ; { sleep 10; } ; sleep 10; } } | watch "ps -H"

Now in the resulting watch window we get a update every 2 seconds of what's going on:

Here's the first sleep 10:

  PID TTY          TIME CMD
29190 pts/1    00:00:00 bash
 5676 pts/1    00:00:00   bash
 5678 pts/1    00:00:00     sleep
 5677 pts/1    00:00:00   watch
 5681 pts/1    00:00:00     watch
 5682 pts/1    00:00:00       ps

Here's the second sleep 10:

  PID TTY          TIME CMD
29190 pts/1    00:00:00 bash
 5676 pts/1    00:00:00   bash
 5691 pts/1    00:00:00     sleep
 5677 pts/1    00:00:00   watch
 5694 pts/1    00:00:00     watch
 5695 pts/1    00:00:00       ps

Here's the third sleep 10:

  PID TTY          TIME CMD
29190 pts/1    00:00:00 bash
 5676 pts/1    00:00:00   bash
 5704 pts/1    00:00:00     sleep
 5677 pts/1    00:00:00   watch
 5710 pts/1    00:00:00     watch
 5711 pts/1    00:00:00       ps

Notice all three sleeps though invoked at different nesting levels of curly braces do infact stay within the PID 5676 of Bash. So I believe your issue is self inflicted with the use of | ps -H.

Conclusions

The use of | ps -H (i.e. the pipe) is causing an additional sub-shell, so don't use that method when attempting to interrogate what's going on.

share|improve this answer
    
slm, thank you for the great tips! –  1_CR May 1 at 13:03

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.

$ { A=1; { A=2; sleep 2; } ; echo $A; }
2

$ { A=1; { A=2; sleep 2; } | sleep 1; echo $A; }
1
share|improve this answer
    
My A is showing this as well. –  slm Apr 30 at 18:57
    
@slm Right, didn't see that part of your answer when I was posting. Anyway, I had fun with this and learned something new; as discussed with OP we see it's happening but actually is it necessity? I remember I was surprised to find out that calling function in pipeline runs it in sub-shell too. I guess calling bash builtins is not invoking sub-shell, that would be very surprising if it did. –  Miroslav Koskar Apr 30 at 19:05

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.