Shell变量可以具有哪些作用域?


42

我只是遇到一个问题,表明我不清楚shell变量的范围。

我试图使用bundle install,这是一个Ruby命令,使用的值$GEM_HOME来完成其工作。我已经设置了$GEM_HOME,但是命令忽略了这个值,直到我使用为止export,如上图所示export GEM_HOME=/some/path

我读到这使变量以某种方式成为“全局”(也称为环境变量),但是我不明白那是什么意思。我了解编程方面的全局变量,但不涉及不同的程序。

而且,鉴于我对此类变量的设置仅适用于当前的shell会话,我将如何为守护进程设置它们?

Shell变量可以具有哪些作用域?

Answers:


33

该工艺采用树状结构:每个进程都有一个唯一的父,除了initPID始终是1,并没有父。

新流程的创建通常通过一对fork/ execv系统调用进行,其中子流程的环境是父流程的副本

要将变量从外壳放置到环境中,必须export将该变量放入环境中,以便对所有子级递归可见。但是请注意,如果子项更改了变量的值,则更改后的值仅对它以及更改创建的所有进程(如前所述,为副本)可见。

还应考虑到子进程可能会更改其环境,例如,可以将其重置为默认值,例如可能会这样做login


1
啊! 好,让我们看看我是否理解这一点。在外壳程序中,如果我说FOO=bar,那将设置当前外壳程序进程的值。如果再运行(bundle install)之类的程序,则会创建一个子进程,该子进程无法访问FOO。但是,如果我说过export FOO=bar,子进程(及其子进程)可以访问它。其中之一可以依次调用export FOO=buzz以更改其后代的值,或仅FOO=buzz更改其自身的值。那是对的吗?
内森·朗

2
@NathanLong并非如此:在所有现代shell中,变量要么导出(因此值的任何更改都反映在后代的环境中),要么不导出(意味着该变量不在环境中)。特别是,如果在启动外壳程序时变量已经在环境中,则将其导出。
吉尔(Gilles)'所以

2
我对句子“如果一个孩子更改变量的值,更改后的值仅对它以及更改后创建的所有进程可见”一词感到困惑。说“ ...对它及其在更改后创建的所有后代进程可见”会更正确-父进程的其他子进程,即使是在子进程之后启动的子进程也不会受到影响。
Jaan 2015年

26

至少在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中,您可以使用完全全局且由所有进程共享的系统变量。


12

它们按过程确定范围

其他的回答者帮助我理解了shell变量范围是关于进程及其后代的

当您ls在命令行上键入命令时,实际上是在派生一个进程来运行该ls程序。新进程将您的外壳作为其父级。

任何进程都可以具有自己的“局部”变量,这些变量不会传递给子进程。它还可以设置“环境”变量。使用export创建一个环境变量。无论哪种情况,不相关的进程(原始伙伴)都不会看到该变量;我们仅控制子进程看到的内容。

假设您有一个bash shell,我们将其称为A。键入bash,将创建一个子进程bash shell,我们将其称为B。您export在A中调用的所有内容仍将在B中设置。

现在,在B中,您说FOO=b。将发生两件事之一:

  • 如果B没有(从A那里)接收到一个名为的环境变量FOO,它将创建一个局部变量。B的孩子不会得到它(除非B打电话export)。
  • 如果B 确实从A接收到一个被调用的环境变量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

1
当您说“它将对其自身及其子级进行修改”时,您应阐明只有修改创建的子级才能看到修改后的值。
enzotib 2012年

1
@enzotib-好点。更新。
内森·朗

3

关于导出,我能找到的最好的解释是:

http://tldp.org/LDP/Bash-Beginners-Guide/html/sect_03_02.html

子外壳程序或子外壳程序中设置的变量仅对定义它的子外壳程序可见。实际将导出的变量设置为环境变量。为了清楚起见,您将bundle install执行自己的外壳程序,$GEM_HOME除非将其设为已environment导出的变量,否则不会看到。

您可以在此处查看变量范围的文档:

http://www.tldp.org/LDP/abs/html/subshel​​ls.html


嗯,所以我不正确地使用术语“环境变量” FOO=bar;您必须使用export它。问题已相应纠正。
内森·朗

看看我添加的链接
。– Karlson

3

正如预期的那样,存在可变作用域的层次结构。

环境

最外部的范围是环境。这是操作系统管理的唯一范围,因此可以保证每个进程都存在。当进程启动时,它将收到其父级环境的副本,此后两者将成为独立环境:修改子级环境不会更改父级环境,并且修改父级环境不会更改已经存在的子级环境。

外壳变量

Shell具有自己的变量概念。这使事情开始变得有些混乱。

当您为外壳程序中的变量分配值并且该变量已在环境中存在时,环境变量将接收新值。但是,如果该变量不在环境中,则它将成为外壳程序变量。Shell变量仅存在于Shell进程中,类似于Ruby变量仅存在于Ruby脚本中的方式。它们永远不会被子进程继承。

这是export关键字起作用的地方。它将shell变量复制到shell进程的环境中,使子进程可以继承。

局部变量

局部变量是shell变量,范围仅限于包含它们的代码块。您可以使用typeset关键字(portable)或localor declare(Bash)声明局部变量。像其他shell变量一样,局部变量也不由子进程继承。同样,局部变量也不能导出。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.