我刚刚遇到一个问题,表明我不清楚 shell 变量的范围。
我试图使用bundle install
,这是一个 Ruby 命令,它使用 的值来$GEM_HOME
完成其工作。我已经设置了$GEM_HOME
,但命令忽略了该值,直到我使用export
,如 中所示export GEM_HOME=/some/path
。
我读到这使得变量在某种程度上“全局”(也称为环境变量),但我不明白这意味着什么。我了解编程中的全局变量,但不了解不同程序的全局变量。
另外,鉴于我设置的此类变量仅适用于当前 shell 会话,那么我将如何为守护进程等设置它们?
shell 变量可以有哪些作用域?
答案1
进程被组织为树:每个进程都有一个唯一的父进程,除此之外init
始终PID
为 1 并且没有父进程。
新进程的创建一般要经过一对fork
/execv
系统调用,其中子进程的环境是复制父进程的。
要将变量从 shell 放入环境中,您必须使用export
该变量,以便所有子级都可以递归地看到它。但请注意,如果子进程更改了变量的值,则更改后的值仅对其和创建的所有进程可见后该变化(作为复制,如前所述)。
还要考虑到子进程可能会更改其环境,例如可以将其重置为默认值,例如可能会这样做login
。
答案2
至少在ksh
and下bash
,变量可以有三范围,不二就像目前所有剩余的答案一样。
除了导出的(即环境)变量和 shell 未导出的变量范围之外,还有第三个较窄的范围用于函数局部变量。
在 shell 函数中使用typeset
标记声明的变量仅在声明它们的函数以及从那里调用的(子)函数中可见。
这个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 之间共享,即它们只能被继承,并且继承的值之后不会受到父 shell 的影响。
请注意,后一种行为与 Windows 中的行为完全不同,在 Windows 中您可以使用完全全局且由所有进程共享的系统变量。
答案3
它们的范围按流程划分
其他回答者帮助我理解 shell 变量范围大约是进程及其后代。
ls
当您在命令行上键入命令时,您实际上是在分叉一个进程来运行该ls
程序。新进程将您的 shell 作为其父进程。
任何进程都可以有自己的“局部”变量,这些变量不会传递给子进程。它还可以设置“环境”变量,即。使用export
创建一个环境变量。在任何一种情况下,不相关的进程(原始进程的同级进程)都不会看到该变量;我们只是控制子进程看到的内容。
假设您有一个 bash shell,我们将其称为 A。您输入bash
,这将创建一个子进程 bash shell,我们将其称为 B。您export
在 A 中调用的任何内容仍将在 B 中设置。
现在,在 B 中,你说FOO=b
。将会发生以下两种情况之一:
- 如果 B 没有(从 A)接收到名为 的环境变量
FOO
,它将创建一个局部变量。 B 的孩子不会得到它(除非 B 调用export
)。 - 如果乙做过接收(从 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
,所以子进程没有收到shell的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
答案4
我能找到的关于导出的最好解释是:
http://tldp.org/LDP/Bash-Beginners-Guide/html/sect_03_02.html
子 shell 或子 shell 中设置的变量仅对定义它的子 shell 可见。导出的变量实际上是一个环境变量。因此,需要明确的是,您执行自己的 shell ,除非将其设置为变量(又名导出),否则bundle install
它不会看到。$GEM_HOME
environment
您可以在此处查看变量范围的文档: