5

From what I gather from the online docs, the following should spawn a subshell for the part of the command embedded with {}:

$ bash -c '{ sleep 10; echo "Sleeping process", $$; }  & echo $$; '
11237
 Sleeping process, 11237

However as you can see, in both cases the process ID is the same. What am I missing? Thanks for any pointers.

2
  • 3
    $$ always returns the process ID of the parent shell's process, even if the child shell is running in a different process.
    – chepner
    Commented Oct 8, 2013 at 13:34
  • Yes, indeed. I found it later on and I added it to my answer. Thanks for your comment.
    – Amit
    Commented Oct 8, 2013 at 21:57

2 Answers 2

7

The {} just groups commands together in the current shell, while () starts a new subshell. However, what you're doing is putting the grouped commands into the background, which is indeed a new process; if it was in the current process, it could not possibly be backgrounded. It's easier, IMHO, to see this kind of thing with strace:

sauer@humpy:~$ strace -f -etrace=process bash -c '{ sleep 10; echo "Sleeping process", $BASHPID, $BASH_SUBSHELL; }  & echo $BASHPID;' > /tmp/file
execve("/bin/bash", ["bash", "-c", "{ sleep 10; echo \"Sleeping proce"...], [/* 20 vars */]) = 0
arch_prctl(ARCH_SET_FS, 0x7f15a90da700) = 0
clone(Process 25347 attached
child_stack=0, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7f15a90da9d0) = 25347
[pid 25346] exit_group(0)               = ?
clone(Process 25348 attached (waiting for parent)
Process 25348 resumed (parent 25347 ready)
child_stack=0, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7f15a90da9d0) = 25348
[pid 25348] execve("/bin/sleep", ["sleep", "10"], [/* 20 vars */] <unfinished ...>
[pid 25347] wait4(-1, Process 25347 suspended
 <unfinished ...>
[pid 25348] <... execve resumed> )      = 0
[pid 25348] arch_prctl(ARCH_SET_FS, 0x7f922ad16700) = 0
[pid 25348] exit_group(0)               = ?
Process 25347 resumed
Process 25348 detached
<... wait4 resumed> [{WIFEXITED(s) && WEXITSTATUS(s) == 0}], 0, NULL) = 25348
--- SIGCHLD (Child exited) @ 0 (0) ---
wait4(-1, 0x7fffaa432ad8, WNOHANG, NULL) = -1 ECHILD (No child processes)
exit_group(0)                           = ?
Process 25347 detached

sauer@humpy:~$ cat /tmp/file
25346
Sleeping process, 25347, 1

Note that the bash command starts, then it creates a new child with clone(). Using the -f option to strace means it also follows child processes, showing yet another fork (well, "clone") when it runs sleep. If you leave the -f off, you see just the one clone call when it creates the backgrounded process:

sauer@humpy:~$ strace -etrace=clone bash -c '{ sleep 10; echo "Sleeping process", $BASHPID, $BASH_SUBSHELL; }  & echo $BASHPID;' > /tmp/file
clone(child_stack=0, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7f2bdd5399d0) = 26394
sauer@humpy:~$ strace -etrace=process bash -c '{ sleep 10; echo "Sleeping process", $BASHPID, $BASH_SUBSHELL; }  & echo $BASHPID;' > /dev/null
execve("/bin/bash", ["bash", "-c", "{ sleep 10; echo \"Sleeping proce"...], [/* 20 vars */]) = 0
arch_prctl(ARCH_SET_FS, 0x7fd01ae86700) = 0
clone(child_stack=0, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7fd01ae869d0) = 26706
exit_group(0)                           = ?

If you really just want to know how often you're creating new processes, you can simplify that even further by only watching for fork and clone calls:

sauer@humpy:~$ strace -etrace=fork,clone bash -c '{ sleep 10; echo "Sleeping process", $BASHPID, $BASH_SUBSHELL; }  & echo $BASHPID;' > /dev/null
clone(child_stack=0, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7f467fa769d0) = 27025
1
  • 1
    Thanks very much for clarifying. So, I stand corrected about my {} actually not creating a subshell. Also, thanks for showing me an easy way to follow the fork and clone system calls. Thanks.
    – Amit
    Commented Oct 7, 2013 at 0:15
5

Update:

Please see the answer above. With {}, I am not really creating a subshell. Instead, the back grounding here gives me the answer I was looking for.

Okay, so basically I was using the incorrect "indicator" of subshells being created. I learned about the BASHPID from here and using that in addition to BASH_SUBSHELL, I can see that a subshell is indeed being created.

Test command:

$ bash -c '{ sleep 10; echo "Sleeping process", $BASHPID, $BASH_SUBSHELL; }  & echo $BASHPID; '
12074
 Sleeping process, 12075, 1

Another test command to also show the parent process ID for both the shell and the subshell:

$ bash -c '{ sleep 10; echo "Sleeping process", $BASHPID, $BASH_SUBSHELL, $PPID; }  & echo $BASHPID, $PPID; '
12411, 9128
$ Sleeping process, 12412, 1, 9128
1

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .