shell 变量可以有哪些作用域?

Nat*_*ong 44 environment-variables

我刚遇到一个问题,显示我不清楚 shell 变量的范围。

我试图使用bundle install,这是一个 Ruby 命令,它使用 的值$GEM_HOME来完成其工作。我已经设置了$GEM_HOME,但是在我使用之前,该命令忽略了该值export,如export GEM_HOME=/some/path.

我读到这使变量以某种方式“全局”(也称为环境变量),但我不明白这意味着什么。我了解编程中的全局变量,但不了解不同的程序。

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

shell 变量可以有哪些作用域?

enz*_*tib 36

这些进程被组织成一棵树:每个进程都有一个唯一的父进程,除此之外,initPID总是为 1 并且没有父进程。

新进程的创建一般要经过一对fork/execv系统调用,其中子进程的环境是父进程的副本

要将变量从 shell 放入环境中,您必须使用export该变量,以便它对所有子项递归可见。但请注意,如果子进程更改了变量的值,则更改后的值仅对其可见,并且该更改之后创建的所有进程(如前面所述的副本)可见。

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

  • @NathanLong 不完全是这样:在所有现代 shell 中,变量要么被导出(因此任何值的变化都会反映在后代的环境中)或不导出(意味着变量不在环境中)。特别是,如果该变量在 shell 启动时已在环境中,则将其导出。 (2认同)
  • 我对这句话有点困惑“如果一个孩子改变了一个变量的值,改变后的值只对它可见,并且在该改变之后创建的所有进程”。更正确的说法是“...对它以及在该更改后创建的所有子进程可见”——父进程的其他子进程,即使是在子进程之后启动的子进程,也不受影响。 (2认同)

jll*_*gre 30

至少在kshand下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 
Run Code Online (Sandbox Code Playgroud)

产生这个输出:

in function:
[one] [two] [three]
in shell:
[one] [two] []
in subshell:
[one] [] []
in other shell
[] [] []
Run Code Online (Sandbox Code Playgroud)

如您所见,导出的变量从前三个位置显示,未导出的变量不会显示在当前 shell 之外,函数局部变量在函数本身之外没有任何值。最后一个测试根本没有显示任何值,这是因为导出的变量在 shell 之间不共享,即它们只能被继承,并且继承的值之后不会受到父 shell 的影响。

请注意,后一种行为与 Windows 完全不同,在 Windows 中您可以使用完全全局且由所有进程共享的系统变量。


Nat*_*ong 14

它们受流程的限制

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

当您ls在命令行上键入命令时,您实际上是在创建一个进程来运行ls程序。新进程将您的 shell 作为其父进程。

任何进程都可以有自己的“本地”变量,这些变量不会传递给子进程。它还可以设置“环境”变量,它们是。使用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'
Run Code Online (Sandbox Code Playgroud)

所有这些都解释了我最初的问题:我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
Run Code Online (Sandbox Code Playgroud)