No, the subshell was created first.
A shell execution environment contains shell parameters set by variable assignments and environment variables. A subshell environment was created by duplicating the shell environment, so it contains all the variables of the current shell environment.
See the example:
$ b=1
$ c=$(b=2; echo "$b")
$ echo "$c"
2
The output is 2
instead of 1
.
A subshell environment created by command substitution is different with a shell environment created by calling the shell executable.
When you call the shell as:
$ bash -c :
the the current shell used execve() to create new shell process, something like:
execve("/bin/bash", ["bash", "-c", ":"], [/* 64 vars */]) = 0
the last argument passed to execve
contains all the environment variables.
That's why you need to export the variables to push it to the environment variables, which will be included in subsequently executed commands:
$ a=; export a
$ strace -e execve bash -c :
execve("/bin/bash", ["bash", "-c", ":"], [/* 65 vars */]) = 0
+++ exited with 0 +++
Notice the environment variables change from 64 to 65. And variables which are not exported will not be passed to new shell environment:
$ a=; b=; export a
$ strace -e execve bash -c :
execve("/bin/bash", ["bash", "-c", ":"], [/* 65 vars */]) = 0
+++ exited with 0 +++
Notice the environment variables are still 65.
In command substitution, the shell used fork() to create new shell process, which just copied the current shell environment - which contains both variables set and environment variables.
A subshell duplicates the existing shell. It has the same variables¹, the same functions, the same options, etc. Under the hood, a subshell is created with the fork
system call²; the child process goes on to do what is expected of it while the parent waits (e.g., $(…)
) or goes on with its life (e.g., … &
) or otherwise does what is expected of it (e.g., … | …
).
sh -c …
does not create a subshell. It launches another program. That program happens to be a shell, but that's just a coincidence. The program may even be a different shell (e.g., if you run sh -c …
from bash, and sh
is dash), i.e., a completely different program that just happens to have significant similarities in its behavior. Under the hood, launching an external command (sh
or any other) calls the fork
system call and then the execve
system call to replace the shell program in the subprocess by another program (here sh
).
¹ Including $$
, but excluding some shell-specific variables such as bash and mksh's BASHPID
.
² At least, that's the traditional and usual implementation. Shells can optimize the fork away if they can mimic the behavior otherwise.
Relevant man pages: fork(2), execve(2).
Best Answer
In the case of
bash
, that depends on whether$SHELLOPTS
is in the environment or not.See how the
bash -c 'echo x'
inherited thextrace
option. For the options set byshopt
, it's the same but with the$BASHOPTS
variable.It comes handy especially for the
xtrace
option for debugging when you want to run abash
script (or any command running abash
script) and all otherbash
script it may invoke, recursively withxtrace
(provided nothing does aset +x
in those scripts). If yoursh
isbash
, that will also affect them, so also thesystem("command line")
made in other languages: