Why is a variable visible in a subshell?

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)
$ ./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?


Thank you for visiting the Q&A section on Magenaut. Please note that all the answers may not help you solve the issue immediately. So please treat them as advisements. If you found the post helpful (or not), leave a comment & I’ll get back to you as soon as possible.

Method 1

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.

Method 2

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.

All methods was sourced from stackoverflow.com or stackexchange.com, is licensed under cc by-sa 2.5, cc by-sa 3.0 and cc by-sa 4.0

0 0 votes
Article Rating
Notify of

Inline Feedbacks
View all comments
Would love your thoughts, please comment.x