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
./file, but why in the
()case does the subshell identify the
varvariable although it is not exported and in the
./filecase 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
straceto 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
./fileshould 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
./filecase, 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.
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.
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 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