10

My understanding of running commands in subshells is that the current shell is forked, and the subshell then does a further fork and exec on the desired commands.

I see strange behaviour when running the following in both bash and zsh:
$ ( sleep 5 && sleep 6 )

When running in bash:

$ echo $$
6410
$ ( sleep 5 && sleep 6 )

ps gives the following:
CMD: sleep 5
PID: 6590
PPID: 6589

and then:
CMD: sleep 6
PID: 6616
PPID: 6589

This all makes sense to me. sleep 5 and sleep 6 both have the same parent (presumably the subshell).

However, in zsh, I get the following:

$ echo $$
1987
$ ( sleep 5 && sleep 6 )

ps gives the following:
CMD: sleep 5
PID: 7576
PPID: 7575

and then:
CMD: sleep 6
PID: 7575
PPID: 1987

I can't make head or tail of why sleep 5 has the process used to run sleep 6 as its parent and the process used to run sleep 6 has the original shell as its parent?

5
  • Interesting question ... note that the behavior you're seeing in zsh seems to meet the specification for the ( ) operator. The sleep processes do not run in the first-level shell (1987 in your example), they do indeed run as subprocesses. I don't think there's a requirement that the processes have a common parent.
    – Gairfowl
    May 8, 2022 at 13:02
  • 1
    @Gairfowl, I don't think there's any requirement for the subshell to actually run in a different process in any case. Just that it runs in a different shell execution environment. Sure, a simple way of doing that is to fork the process. But it's not a requirement if the shell cares to take care of it within one process.
    – ilkkachu
    May 8, 2022 at 13:07
  • Which is kinda why I feel like asking: Does it matter? What does it matter what the PIDs are, if the script works correctly?
    – ilkkachu
    May 8, 2022 at 13:08
  • 1
    Could be tail call optimisation. If you can not tell the difference (without looking inside the box), then it is free to optimise. May 8, 2022 at 13:11
  • 1
    @ilkkachu - looks like your are correct, posix apparently does not require that a shell execution environment be within a self-contained process. That's going to mess up a script I wrote three companies ago that depended on a subshell being kill-able :). I doubt the OP is worried about correct execution - my impression is that they are just trying to figure out how the world works ...
    – Gairfowl
    May 8, 2022 at 13:20

1 Answer 1

13

You can get a similar thing in Bash:

$ echo $$
26328
$ ( /bin/sleep 1234 )
$ ps -o pid,ppid,stat,args -C sleep
  PID  PPID STAT COMMAND
26473 26328 S+   /bin/sleep 1234

What Bash does there, is that while it does fork itself for the subshell, it recognizes there's only one command to run there, and then just exec's the sleep over itself as an optimization, saving the extra fork. It does the same for something like bash -c '/bin/sleep'

Just that Bash only does that for the case of one command, with ( true; /bin/sleep 2345 ), you'd see the intermediate shell process too. Apparently zsh is more aggressive with that optimization and skips the fork for the last command of the list, too.


In any case, there's no requirement that a subshell run in another process. Just that it runs in another shell execution environment, a phrase from the POSIX shell language specification. Now, that environment does include things like the umask and the current working directory (things that are per-process on the OS level), as well as shell variables etc., so implementing separate environments via forking is the easy way. But it's not a requirement.

And e.g. ksh does something different, there's no fork in this simple subshell at all. But it still works, the value of FOO we get is from the outer shell level:

$ strace -etrace=clone,fork,vfork -f ksh -c 'FOO=out; ( FOO=in; true ); echo "$FOO"'
out
+++ exited with 0 +++

Bash and zsh would fork there. Also ksh implements sleep as builtin, so you won't see process for it there, unless you use /bin/sleep explicitly.

1
  • 1
    Note that zsh disables that optimisation if there are traps that are defined (so (trap "echo done" EXIT; sleep 1) correctly outputs done for instance). A few shells that were also doing that optimisation failed to disable it in that case causing bugs. IIRC ksh93 still has related bugs. May 9, 2022 at 15:58

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.