4

After running the command

{ sleep 5; } &  

output of ps is (output 1)

 PID TTY           TIME CMD
  972 ttys000    0:00.27 -bash
 2556 ttys000    0:00.00 -bash
 2557 ttys000    0:00.00 sleep 5   

while for

( sleep 5 ) &    

out of ps is (output 2)

PID TTY           TIME CMD
 972 ttys000    0:00.28 -bash
2566 ttys000    0:00.00 sleep 5  

() causes a subshell environment and I expect "output 1" for this case as it results in forking a child process while I expects "output 2" for { sleep 5; } & as it executes in the current shell. This might look a silly question but I really do not understand this behavior.
What I am missing here?

10

Bash will run the last or only command of a subshell with exec if it figures it can do so safely, as an optimisation. You can validate this clearly with pstree:

$ pstree $$
bash---pstree
$ ( pstree $$ )
bash---pstree
$ ( pstree $$ ; echo)
bash---bash---pstree

$ 

That is why your ( sleep 5 ) shows up as just the sleep command, and no intermediate shell. In the last example above, the echo forces the shell to do something after pstree completes, so there's a real shell around to use; in the middle case, the spawned shell just immediately execs pstree, so it looks the same as the first case (which is standard fork-exec). Many shells do this.


On the other hand, anything run in the background requires spawning a new process: fundamentally, that's what "background" is. Commands in braces do ordinarily run in the current shell, but that can't happen if they're to run in the background. So that the parent shell can keep doing whatever it's doing while the background commands run, a new shell has to be created to run the whole { ... } in. Thus

$ { pstree $$ ; }
bash---pstree
$ { pstree $$ ; } &
bash---bash---pstree

In this case, there's no difference whether there's another trailing command or not. Bash documents this behaviour for &:

If a command is terminated by the control operator ‘&’, the shell executes the command asynchronously in a subshell. This is known as executing the command in the background. The shell does not wait for the command to finish, and the return status is 0 (true).

You can also see this happen by backgrounding a built-in command like read:

$ read &
$ pstree $$
[1] 32394
[1]+  Stopped                 read
$ pstree $$
bash-+-bash
     `-pstree

My shell now has two children: a bash running read, and the pstree command printing that output.


It's true that the shell could make a further optimisation and apply it to backgrounded { ... } as it does to parenthesised subshells, but it doesn't. It's possible some other shells do, but I haven't found one in quick testing. It's a pretty rare case and formally different, and there are some odd corner cases.

  • Generally, the most optimised shell when it comes to saving processes would be ksh93 and the least optimised bash with other shells sitting somewhere it between. – Stéphane Chazelas Mar 13 '18 at 21:08
  • anything run in the background requires spawning a new process: so does (sleep 2) &. But it looks same as the case (sleep 2). Even (sleep 2, echo) &1 looks same as (sleep 2) . – haccks Mar 13 '18 at 21:17
  • Yes, the only difference between those is whether the parent waits for the child to finish or not. – Michael Homer Mar 13 '18 at 21:20
  • @MichaelHomer; In both cases, (sleep 2) & and { sleep 2; } &, parent doesn't wait for the child to finish as per the behavior of &. – haccks Mar 13 '18 at 21:23
  • 1
    (sleep 2) was already a separate process, with one optimised away (first half of the answer), and the only difference the & makes is not waiting for it to finish; { sleep 2; } & becomes a Bash process, which then spawns sleep 2 (second half of the answer). They are not the same. – Michael Homer Mar 14 '18 at 3:24

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.