1

While investigating the problem of redirecting the stderr of the bash keyword time and the standard solution to the problem, I came across another question. The description man bash gives of the (list) compound command is list is executed in a subshell environment. Now, as you can see from the execution of the little program hello- which makes the simple system("ps -opid,ppid,comm") call, it looks there is no difference at all between the (./hello) and ./hello command lines. That came as a surprise to me. However I understand there is a difference when the command is a built-in one. In this case, with no parentheses no fork will take place but one would with the parentheses around the built-in command. Am I missing anything?

18:59:13 -> echo $$
5323
19:34:30 -> ./hello
hello world from process ID 8657, parent ID 5323
  PID  PPID COMM
 5323  5322 -bash
 8657  5323 ./hello
19:42:34 -> (./hello)
hello world from process ID 8977, parent ID 5323
  PID  PPID COMM
 5323  5322 -bash
 8977  5323 ./hello
19:42:43 -> time (./hello)
hello world from process ID 8985, parent ID 8984
  PID  PPID COMM
 5323  5322 -bash
 8984  5323 -bash
 8985  8984 ./hello

real    0m0.021s
user    0m0.003s
sys 0m0.004s
19:42:53 -> (time ./hello)
hello world from process ID 9000, parent ID 8999
  PID  PPID COMM
 5323  5322 -bash
 8999  5323 -bash
 9000  8999 ./hello

real    0m0.030s
user    0m0.003s
sys 0m0.005s
19:43:05 ->

1 Answer 1

2

A subshell isn't guaranteed to run in a separate process. That's just the usual implementation.

The definition of a subshell is that shell must ensure that the subshell doesn't change the state of its parent: it must not affect the parent's variables, defined functions, aliases, options, history, limits, etc. The easiest way to do that is to run the subshell in a separate process, so that's usually what happens. But if the shell can be sure that the subshell won't affect anything in the parent, it doesn't have to run a separate process.

With most sh implementations, a subshell that does nothing but run an external command doesn't fork twice.

for sh in dash bash ksh93 mksh zsh; do printf "%6s " "$sh"; "$sh" -c '(perl -e "print getppid()"); echo " $$"'; done
  dash 945 945
  bash 947 947
 ksh93 949 949
  mksh 951 951
   zsh 953 953

As soon as you do something more complicated, bash forks for the subshell and forks again for the external command. Some other shells don't fork for the last command of a (sub)shell (unless there's a trap).

for sh in dash bash ksh93 mksh zsh; do printf "%6s " "$sh"; "$sh" -c '(true; perl -e "print getppid()"); echo " $$"'; done
  dash 1306 1306
  bash 1309 1308
 ksh93 1311 1311
  mksh 1314 1313
   zsh 1316 1316

ATT ksh (ksh93) optimizes more than other shells. Of the five I ran in my comparison, it's the only one that optimizes by creating a separate data structure in the same process to keep track of changing data in the subshell.

for sh in dash bash ksh93 mksh zsh; do printf "%6s " "$sh"; "$sh" -c '(perl -e "print getppid()"; /bin/true); echo " $$"'; done
  dash 3082 3081
  bash 3085 3084
 ksh93 3088 3088
  mksh 3092 3091
   zsh 3096 3095

There are things that force even ATT ksh to create a subprocess for the subshell, for example changing a process limit.

for sh in dash bash ksh93 mksh zsh; do printf "%6s " "$sh"; "$sh" -c '(ulimit -n 42; perl -e "print getppid()"; /bin/true); echo " $$"'; done
  dash 4504 4503
  bash 4507 4506
 ksh93 4511 4510
  mksh 4515 4514
   zsh 4519 4518
3
  • Understood. Still I don't get what's the point in saying list is executed in a subshell environment when (list) and list are processed exactly in the same way.
    – diciotto
    May 5, 2020 at 20:49
  • @diciotto In general, (list) and list are not processed in the same way. Compare a=out; (a=in); echo $a and a=out; a=in; echo $a. In the specific case where list is a single external command, they are processed in the same way, because in this specific case there's no observable difference except for the PID. May 5, 2020 at 21:44
  • OK, thanks. That's why I wrote "I understand there would be a difference if the command were a built-in one" (just as in the example you made).
    – diciotto
    May 5, 2020 at 22:14

You must log in to answer this question.

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