23

The Learning Bash Book mentions that a subshell will inherit only environment variables and file descriptors, etc., and that it will not inherit variables that are not exported:

$ var=15
$ (echo $var)
15
$ ./file # this file include the same command echo $var

$

As I know the shell will create two subshells for () and for ./file, but why in the () case does the subshell identify the var variable although it is not exported and in the ./file case it did not identify it?

# Strace for () 
clone(child_stack=0, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7f24558b1a10) = 25617
# Strace for ./file
clone(child_stack=0, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7f24558b1a10) = 25631

I tried to use strace to figure out how this happens and surprisingly I found that bash will use the same arguments for the clone system call, so this means that both the forked process in () and ./file should have the same process address space of the parent, so why in the () case is the varible visible to the subshell and the same does not happen for the ./file case, although the same arguments is based on the clone system call?

2 Answers2

20

Either you or the book is confusing a subshell with a subprocess that is a shell.

Some shell constructs result in the shell forking a child process. Under Linux, fork is a special case of the more general clone system call, which you observed in the strace log. The child runs a part of the shell script. The child process is called a subshell. The most direct such construct is command1 &: command1 runs in a subshell, and subsequent commands run in the parent shell. Other constructs that create a subshell include command substitution $(command2) and pipes command3 | command4 (command3 runs in a subshell, command4 runs in a subshell in most shells but not in ksh or zsh).

A subshell is a copy of the parent process, so it has not only the same environment variables, but also all the same internal definitions: variables (including $$, the process ID of the original shell process), functions, aliases, options, etc. Before executing the code in the subshell, bash sets the variable BASHPID to the process ID of the child process.

When you run ./file, this executes an external command. First, the shell forks a child process; then this child process executes (with the execve system call) the executable file ./file. A child process inherits process attributes of its parents: environment, current directory, etc. Internal aspects of the application are lost in the execve call: non-exported variables, functions, etc. are bash notions that the kernel doesn't know about, and they are lost when bash executes another program. Even if that other program happens to be a bash script, it is executed by a new instance of bash that doesn't know or care that its parent process happens to also be an instance of bash. Thus a shell variable (non-exported variable) doesn't survive execve.

  • This answer cleared up quite a few things for me. The only thing I don't understand is this sentence in the second paragraph: "The child runs a part of the shell script." What shell script is being referred to? – flow2k Feb 27 '18 at 09:24
  • @flow2k The script (i.e. the program) that the shell is interpreting. – Gilles 'SO- stop being evil' Feb 27 '18 at 13:16
18

The Learning Bash Book is wrong. Subshells inherit all variables. Even $$ (the PID of the original shell) is kept. The reason is that for a subshell, the shell just forks and doesn't execute a new shell (on the contrary, when you type ./file, a new command is executed, e.g. a new shell; in the strace output, look at execve and similar). So, basically, it's just a copy (with some documented differences).

Note: this is not specific to bash; this is true for any shell.

vinc17
  • 12,174
  • Oky but i tried now to lauch strace on the shell and i tried to execute ./file but i can't find any call for exec and so the address space should be the same for both processes so how this can be explained? – user3718463 Sep 27 '14 at 22:57
  • @user3718463 Did you use the -f option of strace to trace children too? That's necessary to find the exec's. – vinc17 Sep 27 '14 at 22:58
  • yup i figure it out thanks alot, i was missing the -f option, and so i can't find the exec sys call – user3718463 Sep 27 '14 at 23:01