它们的范围按流程划分

它们的范围按流程划分

我刚刚遇到一个问题,表明我不清楚 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

至少在kshand下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_HOMEenvironment

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

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

相关内容