1

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
Braiam
  • 35,991
Tim
  • 101,790

3 Answers3

7

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.

Jeff Schaller
  • 67,283
  • 35
  • 116
  • 255
cuonglm
  • 153,898
  • rui@ruir:~$ b=1 rui@ruir:~$ bash rui@ruir:~$ echo $b (nothing) – Rui F Ribeiro Feb 11 '16 at 02:34
  • 1
    @RuiFRibeiro: You were confused between child process environment and subshell environment. – cuonglm Feb 11 '16 at 02:35
  • thanks. In my example,b isn't an environment variable, why is c assigned the value of b by command substituion? – Tim Feb 11 '16 at 02:49
  • I took the liberty of editing the English, and made a word change that I think will help @Tim understand better the issue of his last question. – Rui F Ribeiro Feb 11 '16 at 03:00
  • @Rui: Does the reply answer my question in my last comment, which is also in my post? I.e. In my example, b isn't an environment variable, so b doesn't exist in the subshell, why is c assigned the value of b by command substituion? – Tim Feb 11 '16 at 03:20
  • @Tim: I think I can say with a certain degree I got or assumed cuonglm answer which was/is fine by me. I made a more relevant change to his answer, in the last phrase, for it to give away more clearly that when invoking a subshell, a fork is made with all the variables copies of the current/initial shell (the subshell is an exact copy of the original shell). As cuonglm kindly pointed out, I was making the same assumption as you, mistaking the creation of a child process for a subshell, and he proved me wrong. – Rui F Ribeiro Feb 11 '16 at 07:56
  • Thanks for the edit. Can you give the definition of a subshell, so as to distinguish from a shell process invoked by running bash? – Tim Feb 11 '16 at 20:39
  • Also is the case of running a bash script same as running bash -c <command>? – Tim Feb 11 '16 at 20:45
  • @Tim "is the case or running a bash script same as running bash -c <command>". There is a very slight different: When you execute a shell script, the kernel reads the first line, sees the "sh-bang" and tries to execute the program specified after that with the script itself as the first argument. Thus, it's the difference between bash <scriptfile> and bash -c <scriptfile>. In the second form, bash will do an exec with <scriptfile> as an argument, and so ultimately, you get bash <scriptfile> again, but after an additional exec. – Otheus Feb 12 '16 at 00:12
3

Yes b isn't an environment variable.
But: Yes, b does exist in the subshell created by command substitution:

$ b=11; c="$(echo $b)"; echo "$c"          ### b exists in subshell.
11
$ b=11; c="$(b=33; echo $b)"; echo "$c"    ### $b is not replaced before
33                                         ### the subshell is executed.

What does not receive the variables is a "full child process":

$ b=11; bash -c 'echo "<$b>"'              ### b does not exist.
<>
$ b=11 bash -c 'echo "<$b>"'               ### environment b.
<11> 

Except that a process may receive variables in the environment, of course.


Last line at Wooledge SubShell:

In the subshell, the regular shell variable a is visible; but because it is not exported, the full child process does not see it.

2

Command substitution causes a normal call to the shell interpreter and this interpreter will run the echo command in a sub-process.

The sub-process is needed as the shell needs to establish a pipe to the echo command in order to be able to read the results.

The sub-process that runs the echo command is created by fork() and this creates copies of all variables from the main shell process. This is why $b is accessible by the echo command.

schily
  • 19,173