Answers:
该工艺采用树状结构:每个进程都有一个唯一的父,除了init
它PID
始终是1,并没有父。
新流程的创建通常通过一对fork
/ execv
系统调用进行,其中子流程的环境是父流程的副本。
要将变量从外壳放置到环境中,必须export
将该变量放入环境中,以便对所有子级递归可见。但是请注意,如果子项更改了变量的值,则更改后的值仅对它以及更改后创建的所有进程(如前所述,为副本)可见。
还应考虑到子进程可能会更改其环境,例如,可以将其重置为默认值,例如可能会这样做login
。
至少在ksh
和下bash
,变量可以具有三个作用域,而不是像当前所有其余答案所表明的那样两个。
除了导出的(即环境)变量和Shell未导出的变量作用域外,还有第三个较窄的函数局部变量。
在带有typeset
令牌的shell函数中声明的变量仅在它们在从那里调用的(子)函数中声明的函数内部可见。
这个ksh
/ bash
代码:
# Create a shell script named /tmp/show that displays the scoped variables values.
echo 'echo [$environment] [$shell] [$local]' > /tmp/show
chmod +x /tmp/show
# Function local variable declaration
function f
{
typeset local=three
echo "in function":
. /tmp/show
}
# Global variable declaration
export environment=one
# Unexported (i.e. local) variable declaration
shell=two
# Call the function that creates a function local variable and
# display all three variable values from inside the function
f
# Display the three values from outside the function
echo "in shell":
. /tmp/show
# Display the same values from a subshell
echo "in subshell":
/tmp/show
# Display the same values from a disconnected shell (simulated here by a clean environment start)
echo "in other shell"
env -i /tmp/show
产生以下输出:
in function:
[one] [two] [three]
in shell:
[one] [two] []
in subshell:
[one] [] []
in other shell
[] [] []
如您所见,导出的变量从前三个位置显示,未导出的变量不显示在当前外壳程序之外,函数局部变量在函数本身之外没有值。上一个测试根本没有显示任何值,这是因为导出的变量在shell之间不共享,即它们只能被继承,并且继承的值以后不会受到父shell的影响。
请注意,后一种行为与Windows完全不同,在Windows中,您可以使用完全全局且由所有进程共享的系统变量。
其他的回答者帮助我理解了shell变量范围是关于进程及其后代的。
当您ls
在命令行上键入命令时,实际上是在派生一个进程来运行该ls
程序。新进程将您的外壳作为其父级。
任何进程都可以具有自己的“局部”变量,这些变量不会传递给子进程。它还可以设置“环境”变量。使用export
创建一个环境变量。无论哪种情况,不相关的进程(原始伙伴)都不会看到该变量;我们仅控制子进程看到的内容。
假设您有一个bash shell,我们将其称为A。键入bash
,将创建一个子进程bash shell,我们将其称为B。您export
在A中调用的所有内容仍将在B中设置。
现在,在B中,您说FOO=b
。将发生两件事之一:
FOO
,它将创建一个局部变量。B的孩子不会得到它(除非B打电话export
)。FOO
,它将为其自身及其随后的派生子代对其进行修改。B的子代将看到B分配的值。但是,这完全不会影响A。这是一个快速演示。
FOO=a # set "local" environment variable
echo $FOO # 'a'
bash # forks a child process for the new shell
echo $FOO # not set
exit # return to original shell
echo $FOO # still 'a'
export FOO # make FOO an environment variable
bash # fork a new "child" shell
echo $FOO # outputs 'a'
FOO=b # modifies environment (not local) variable
bash # fork "grandchild" shell
echo $FOO # outputs 'b'
exit # back to child shell
exit # back to original shell
echo $FOO # outputs 'a'
所有这些都解释了我的原始问题:我GEM_HOME
在shell中进行设置,但是当我调用bundle install
时,创建了一个子进程。因为我没有使用过export
,所以子进程没有收到外壳程序的GEM_HOME
。
您可以使用来“取消导出”变量-防止将变量传递给子级export -n FOO
。
export FOO=a # Set environment variable
bash # fork a shell
echo $FOO # outputs 'a'
export -n FOO # remove environment var for children
bash # fork a shell
echo $FOO # Not set
exit # back up a level
echo $FOO # outputs 'a' - still a local variable
关于导出,我能找到的最好的解释是:
http://tldp.org/LDP/Bash-Beginners-Guide/html/sect_03_02.html
子外壳程序或子外壳程序中设置的变量仅对定义它的子外壳程序可见。实际将导出的变量设置为环境变量。为了清楚起见,您将bundle install
执行自己的外壳程序,$GEM_HOME
除非将其设为已environment
导出的变量,否则不会看到。
您可以在此处查看变量范围的文档:
FOO=bar
;您必须使用export
它。问题已相应纠正。
正如预期的那样,存在可变作用域的层次结构。
最外部的范围是环境。这是操作系统管理的唯一范围,因此可以保证每个进程都存在。当进程启动时,它将收到其父级环境的副本,此后两者将成为独立环境:修改子级环境不会更改父级环境,并且修改父级环境不会更改已经存在的子级环境。
Shell具有自己的变量概念。这使事情开始变得有些混乱。
当您为外壳程序中的变量分配值并且该变量已在环境中存在时,环境变量将接收新值。但是,如果该变量不在环境中,则它将成为外壳程序变量。Shell变量仅存在于Shell进程中,类似于Ruby变量仅存在于Ruby脚本中的方式。它们永远不会被子进程继承。
这是export
关键字起作用的地方。它将shell变量复制到shell进程的环境中,使子进程可以继承。
局部变量是shell变量,范围仅限于包含它们的代码块。您可以使用typeset
关键字(portable)或local
or declare
(Bash)声明局部变量。像其他shell变量一样,局部变量也不由子进程继承。同样,局部变量也不能导出。
FOO=bar
,那将设置当前外壳程序进程的值。如果再运行(bundle install
)之类的程序,则会创建一个子进程,该子进程无法访问FOO
。但是,如果我说过export FOO=bar
,子进程(及其子进程)将可以访问它。其中之一可以依次调用export FOO=buzz
以更改其后代的值,或仅FOO=buzz
更改其自身的值。那是对的吗?