Update and caveat:
This answer has a troubled past in that I confidently claimed things that turned out not to be true. I believe it has value in its current form, but please help me eliminate other inaccuracies (or convince me that it should be deleted altogether).
I’ve substantially revised – and mostly gutted – this answer after @kojiro pointed out that my testing methods were flawed (I originally used ps
to look for child processes, but that’s too slow to always detect them); a new testing method is described below.
I originally claimed that not all bash subshells run in their own child process, but that turns out not to be true.
As @kojiro states in his answer, some shells – other than bash – DO sometimes avoid creation of child processes for subshells, so, generally speaking in the world of shells, one should not assume that a subshell implies a child process.
As for the OP’s cases in bash (assumes that command{n}
instances are simple commands):
# Case #1
command1 # NO subshell
var=$(command1) # 1 subshell (command substitution)
# Case #2
command1 | command2 # 2 subshells (1 for each pipeline segment)
var=$(command1 | command2) # 3 subshells: + 1 for command subst.
# Case #3
command1 | command2 ; var=$? # 2 subshells (due to the pipeline)
var=$(command1 | command2 ; echo $?) # 3 subshells: + 1 for command subst.;
# note that the extra command doesn't add
# one
It looks like using command substitution ($(...)
) always adds an extra subshell in bash – as does enclosing any command in (...)
.
I believe, but am not certain these results are correct; here’s how I tested (bash 3.2.51 on OS X 10.9.1) – please tell me if this approach is flawed:
- Made sure only 2 interactive bash shells were running: one to run the commands, the other to monitor.
- In the 2nd shell I monitored the
fork()
calls in the 1st withsudo dtruss -t fork -f -p {pidOfShell1}
(the-f
is necessary to also tracefork()
calls “transitively”, i.e. to include those created by subshells themselves). -
Used only the builtin
:
(no-op) in the test commands (to avoid muddling the picture with additionalfork()
calls for external executables); specifically::
$(:)
: | :
$(: | :)
: | :; :
$(: | :; :)
-
Only counted those
dtruss
output lines that contained a non-zero PID (as each child process also reports thefork()
call that created it, but with PID 0). - Subtracted 1 from the resulting number, as running even just a builtin from an interactive shell apparently involves at least 1
fork()
. - Finally, assumed that the resulting count represents the number of subshells created.
Below is what I still believe to be correct from my original post: when bash creates subshells.
bash creates subshells in the following situations:
- for an expression surrounded by parentheses (
(...)
)- except directly inside
[[ ... ]]
, where parentheses are only used for logical grouping.
- except directly inside
- for every segment of a pipeline (
|
), including the first one- Note that every subshell involved is a clone of the original shell in terms of content (process-wise, subshells can be forked from other subshells (before commands are executed)).
Thus, modifications of subshells in earlier pipeline segments do not affect later ones.
(By design, commands in a pipeline are launched simultaneously – sequencing only happens through their connected stdin/stdout pipes.) bash 4.2+
has shell optionlastpipe
(OFF by default), which causes the last pipeline segment NOT to run in a subshell.
- Note that every subshell involved is a clone of the original shell in terms of content (process-wise, subshells can be forked from other subshells (before commands are executed)).
-
for command substitution (
$(...)
) -
for process substitution (
<(...)
)- typically creates 2 subshells; in the case of a simple command, @konsolebox came up with a technique to only create 1: prepend the simple command with
exec
(<(exec ...)
).
- typically creates 2 subshells; in the case of a simple command, @konsolebox came up with a technique to only create 1: prepend the simple command with
- background execution (
&
)
Combining these constructs will result in more than one subshell.