Bash subshell mystery

The Learning Bash book states that the subshell only inherits environment variables and file descriptors, ... etc. and that it will not inherit variables that are not exported from

$ 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 the () case did the subshell define the var variable even though it was not exported, but in the case. / file did she 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 understand how this is happening, and unexpectedly I found that Bash would use the same arguments for the clone system call, which means both forked processes in () and. / file must have the same address space of the parent process, so why in the () case the variable is visible to the subshell, and the same doesn't happen for the case. / file, although the same arguments are based on the clone system call?

+3


source to share


3 answers


A subshell created using parentheses does not use a call execve()

to a new process calling the script. At this point, variables from the parent shell are handled differently: execve()

passing in a deliberate set of variables (the script-calling case) without calling execve()

(the parentheses case), leaving the full set of variables intact.

Your research using usage strace

should have shown this difference accurately; if you haven't seen it, I can only assume that you made one of several possible mistakes. I'll just rip apart what I did to show the difference, then you yourself can decide where your mistake was.

I have created two tracks. The first was done using

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

      

and the second one was done using

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

      



(c x.sh

is the script executable.)

The option -f

was needed to keep track of the children of the parent shell ( bash

on the command line).

These traces that I compared using diff -y -W 300

after aligning all the typical and common differences such as addresses and 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 discovered by looking at the trace files). You will of course have different numbers, so tweak them. And you will need a very wide terminal (over 200 characters) to view the output signal correctly. Make your window wide; -)

Around line 140 I find a call clone()

that is greater than or less than a fork()

, so it splits the current process in two. Around there CHILDREN starts doing things, as we see in the following lines in the track. Around line 165 I see the call execve()

, but only in the trace of the case that the script is calling, so the child voluntarily gives away a lot of his environment and sets deliberate. The parenthesis case doesn't change its environment (it doesn't call execve()

), so the child process continues to have the full set.

+3


source


You must export yours var

for the child process:

export var=15

      

After export, the variable is used for all child processes at startup time (not export time).

var=15
export var

      

coincides with



export var
var=15

      

coincides with



export var=15

      

The export can be canceled with unset

. Example unset var

.

+2


source


The solution to 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, so this will work

$ var=15
$ (echo $var)
15

      

But in the. / File, the subshell will later be followed by the exec or execv system call, which will clear all the previous parent variables, but we still have environment variables, you can check this using strace, using -f to control 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)

      

+1


source







All Articles