Bash – are non-environment variables passed to the subshell invoqued by command substitution

bashshell

Bash manual says:

Command substitution, commands grouped with parentheses, and asynchronous commands are invoked in a subshell environment that is a duplicate of the shell environment,
except that traps caught by the shell are reset to the values that the shell inherited from its parent at invocation.

In this example,b isn't an environment variable, so b doesn't exist in the subshell created by command substitution. Then why is c assigned the value of b by command substituion? Is it because the parameter expansion happens for $b in the shell process before creating a subshell to execute echo 1?

$ b=1
$ c=$(echo $b)
$ echo $c
1

Best Answer

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.