Consider the two following bash commands. One creates a subshell and the other does not.

No subshell

$ bash -c "sleep 10000 "

pstree output:

  └─sleep,103509 10000

With subshell

$ bash -c "sleep 10000; sleep 99999 "

  └─bash,103577 -c sleep 10000; sleep 99999
      └─sleep,103578 10000

Is this some kind of an optimization ? Bash looks at the command and skips the process creation for simple commands. It looks like the simple command is spawned by the parent terminal’s bash.

Bash version I am using is

$ bash --version
GNU bash, version 4.2.46(2)-release (x86_64-redhat-linux-gnu)

Though, I think the same can be observed in bash 3.X too.

Some more examples. With builtins the subshell gets spawned. Builtins are not executed in the parent bash.

$ bash -c "read"

  └─bash,104336 -c read

Same behavior with sleep can be reproduced with top and with output redirect.

$ bash -c "top -b "
  └─top,104392 -b


$ bash -c "top -b > /dev/null "
  └─bash,104420 -c top -b > /dev/null
      └─top,104421 -b

The output of pstree is misleading.

bash -c "sleep 10000 "

does create a bash child process. But this process does not create another child process. Because there is nothing more to do after running sleep the shell makes a direct execve() to sleep without forking first.

Because this is so fast you just see the result after the execve in pstree.

But in the

bash -c "sleep 10000; sleep 99999 "

case the new bash forks twice, once for each command. It could make an execve to the last command, too, instead of forking first. I don't know why it does not.

This and the redirection case are probably just problems with the detection when a fork is necessary.

| improve this answer | |

In the first example, the shell with PID 100648 is your interactive shell, while sleep is in fact the process that replaced bash -c '' process. The execve() syscall spawns a new program which retains the PID of the original process that called it, hence why you don't see that original bash -c '' there. The simple reason for why simple commands replace shell process is because it saves resources as explained in Stephane's answer.

This is also evident from a simple test in one terminal:

$ echo $$
$ bash -c 'sleep 60'

And in another:

$ pstree -p 10250

As for why it happens this way, is because there's just a simple command without pipes, and bash performs direct execve for simple commands.

Exactly for that reason, bash recognizes you have multiple command statements in the second example, hence for each sleep command you have in "sleep 10000; sleep 99999 ". In the first case, execve() can just replace the parent process and when the new exits - no problem. But here shell can't exit after the first command completes. Hence there will be fork and execve for sleep 10000 which is what you see in the output of pstree and later a new fork and execve for sleep 99999

Another test you can perform to believe that simple command in bash -c '' replaces the shell process is this:

$ bash -c 'grep "^Pid:" /proc/self/status /proc/$$/status'
/proc/self/status:Pid:  23946
/proc/23946/status:Pid: 23946

Note, that such behavior is bash-specific. For /bin/dash on Debian-based distros (and also evident in strace , too):

$ sh -c 'grep "^Pid:" /proc/self/status /proc/$$/status'
/proc/self/status:Pid:  24188
/proc/24187/status:Pid: 24187
| improve this answer | |

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.