简体   繁体   中英

Bash subshell mystery

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

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

$

As i know the shell will create two subshells for () case and for ./file, but why in () case the subshell identified 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 the both forked process in () and ./file should have the same process address space of the parent, so why in () case the varible is visable to the subshell and the same does not happen for ./file case although the same arguments is based with clone system call ?

The subshell created using parentheses does not use an execve() call for the new process, the calling of the script does. At this point the variables from the parent shell are handled differently: The execve() passes a deliberate set of variables (the script-calling case) while not calling execve() (the parentheses case) leaves the complete set of variables intact.

Your probing using strace should have shown exactly that difference; if you did not see it, I can only assume that you made one of several possible mistakes. I will just strip down what I did to show the difference, then you can decide for yourself where your error was.

I created two traces. The first was done using

strace -f -o bash-mystery-1.strace bash -c 'v=15; (echo $v)'

and the second was done using

strace -f -o bash-mystery-2.strace bash -c 'v=15; ./x.sh'

(with x.sh being an executable script.)

Option -f was necessary to trace the children of the parent shell (the bash in the command line).

These traces I compared using diff -y -W 300 after equalizing all typical and frequent differences like addresses and the PIDs:

q() {
  sed -e 's/0x[0-9a-f]*/ADDR/g' \
      -e 's/12923\|12927/PARENT/g' \
      -e 's/12924\|12928/CHILD/g'
}
diff -y -W 300 <(q < bash-mystery-1.strace) <(q < bash-mystery-2.strace) | less -S

12923 and 12927 were my parent PIDs and 12924 and 12928 were my child PIDs (which I found out by scanning through the trace files). You will of course have different numbers, so adjust these. And you will need a very wide terminal (more than 200 characters) to view the diff output properly. So make your window wide ;-)

Around line 140 I find a clone() call which is more or less a fork() , so it splits the current process into two. Around there the CHILD starts doing things, as we see in the following lines in the trace. Around line 165 I then see the call of execve() , but only in the trace of the case which calls the script, so there the child voluntarily gives up a lot of its environment and sets a deliberate one. The parentheses case does not change its environment (it does not call execve() ), so the child process continues to have the full set.

You have to export your var for child process:

export var=15

Once exported, the variable is used for all children process at the launch time (not export time).

var=15
export var

is same as

export var
var=15

is same as

export var=15

Export can be cancelled using unset . Sample: unset var .

The solution for this mystery is that subshells inherit everything from the parent shell including all shell variables because they are simply called with fork or clone so they share the same memory space with the parent shell , that's why this will work

$ var=15
$ (echo $var)
15

But in the ./file , the subshell will be later followed by exec or execv system call which will clear all the previous parent variables but we still have the environment variables you can check this out using strace using -f to monitor the child subshell and you will find that there is a call to execv

[pid 26387] execve("./file", ["./file"], [/* 75 vars */]) = -1 ENOEXEC (Exec format error)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM