关于linux:定义带或不带导出的变量

Defining a variable with or without export

export是用来干什么的?

有什么区别:

1
export name=value

1
name=value

export使变量可用于子进程。

也就是说,

1
export name=value

意味着变量名可用于从该shell进程运行的任何进程。如果希望进程使用此变量,请使用export,然后从该shell运行该进程。

1
name=value

表示变量范围仅限于shell,不可用于任何其他进程。您可以将它用于(例如)循环变量、临时变量等。

需要注意的是,导出变量并不能使其对父进程可用。也就是说,在生成的进程中指定和导出变量并不能使它在启动它的进程中可用。


为了说明其他答案的含义:

1
2
3
4
5
6
7
8
9
10
11
$ foo="Hello, World"
$ echo $foo
Hello, World
$ bar="Goodbye"
$ export foo
$ bash
bash-3.2$ echo $foo
Hello, World
bash-3.2$ echo $bar

bash-3.2$


其他人回答说,导出使变量可用于子shell,这是正确的,但只是一个副作用。导出变量时,它将该变量放入当前shell的环境中(即shell调用putenv(3)或setenv(3))。进程的环境在exec中继承,使变量在子shell中可见。

编辑(用5年的视角):这是一个愚蠢的答案。"export"的目的是使变量"处于随后执行命令的环境中",无论这些命令是子shell还是子流程。一个幼稚的实现将简单地将变量放在shell环境中,但这将使实现export -p成为不可能。


有人说,在bash中生成子shell时不需要导出,而其他人则恰恰相反。注意子代码(由()``$()或循环创建的代码)和子进程(按名称调用的进程,例如脚本中出现的文字bash)之间的区别是很重要的。

  • 子shell将可以访问父级中的所有变量,而不管它们的导出状态如何。
  • 子流程将只看到导出的变量。

这两个构造中常见的是,它们都不能将变量传递回父shell。

1
2
3
4
$ noexport=noexport; export export=export; (echo subshell: $noexport $export; subshell=subshell); bash -c 'echo subprocess: $noexport $export; subprocess=subprocess'; echo parent: $subshell $subprocess
subshell: noexport export
subprocess: export
parent:

还有一个困惑的根源:一些人认为"分叉的"子流程是那些看不到未导出变量的子流程。通常fork()后面紧跟exec(),这就是为什么看起来fork()是要查找的东西,而实际上它是exec()。您可以在不使用fork()ing的情况下首先使用exec命令运行命令,并且此方法启动的进程也将无法访问未排序的变量:

1
2
$ noexport=noexport; export export=export; exec bash -c 'echo execd process: $noexport $export; execd=execd'; echo parent: $execd
execd process: export

请注意,我们这次没有看到parent:行,因为我们已经用exec命令替换了父shell,所以不需要执行该命令。


export NAME=value,用于对子流程有意义的设置和变量。

用于当前shell进程专用的临时或循环变量的NAME=value

更详细地说,export标记环境中的变量名,该变量名在创建时复制到子进程及其子进程。从未从子进程复制任何名称或值。

  • 常见的错误是在等号周围放置一个空格:

    1
    2
    $ export FOO ="bar"  
    bash: export: `=': not a valid identifier
  • 子流程只看到导出的变量(B

    1
    2
    $ A="Alice"; export B="Bob"; echo"echo A is \$A. B is \$B" | bash
    A is . B is Bob
  • 子流程中的更改不会更改主shell:

    1
    2
    $ export B="Bob"; echo 'B="Banana"' | bash; echo $B
    Bob
  • 标记为导出的变量在创建子进程时复制了值:

    1
    2
    3
    4
    5
    6
    $ export B="Bob"; echo '(sleep 30; echo"Subprocess 1 has B=$B")' | bash &
    [1] 3306
    $ B="Banana"; echo '(sleep 30; echo"Subprocess 2 has B=$B")' | bash
    Subprocess 1 has B=Bob
    Subprocess 2 has B=Banana
    [1]+  Done         echo '(sleep 30; echo"Subprocess 1 has B=$B")' | bash
  • 只有导出的变量才成为环境的一部分(man environ):

    1
    2
     $ ALICE="Alice"; export BOB="Bob"; env | grep"ALICE\|BOB"
     BOB=Bob

所以,现在应该像夏天的太阳一样晴朗了!感谢Brain Agnew、Alexp和William Prussell。


export将使变量可用于从当前外壳分叉的所有外壳。


应该注意,您可以导出一个变量,然后更改该值。变量的已更改值将可用于子进程。一旦为变量设置了export,则必须执行export -n 以删除该属性。

1
2
3
4
5
6
7
8
$ K=1
$ export K
$ K=2
$ bash -c 'echo ${K-unset}'
2
$ export -n K
$ bash -c 'echo ${K-unset}'
unset


正如您可能已经知道的,Unix允许进程具有一组环境变量,它们是键/值对,键和值都是字符串。操作系统负责为每个进程分别保存这些对。

程序可以通过以下Unix API访问其环境变量:

  • char *getenv(const char *name);
  • int setenv(const char *name, const char *value, int override);
  • int unsetenv(const char *name);

进程还从父进程继承环境变量。操作系统负责在创建子进程时创建所有"envar"的副本。

bash和其他shell一样,能够根据用户请求设置其环境变量。这就是export存在的目的。

export是一个bash命令,用于为bash设置环境变量。用这个命令设置的所有变量都将被这个bash创建的所有进程继承。

关于bash环境的更多信息

bash中的另一种变量是内部变量。由于bash不仅仅是交互式shell,它实际上是一个脚本解释器,与任何其他解释器(如python)一样,它能够保留自己的变量集。应该注意,bash(与python不同)只支持字符串变量。

定义bash变量的符号是name=value。这些变量留在bash中,与操作系统保存的环境变量无关。

关于shell参数(包括变量)的更多信息

同样值得注意的是,根据bash参考手册:

The environment for any simple command or function may be augmented
temporarily by prefixing it with parameter assignments, as described
in Shell Parameters. These assignment statements affect only the
environment seen by that command.

总结一下:

  • export用于设置操作系统中的环境变量。此变量将可用于由当前bash进程创建的所有子进程。
  • bash变量表示法(name=value)用于设置仅对bash的当前进程可用的局部变量。
  • 在另一个命令之前加上bash变量表示法,只为该命令的作用域创建环境变量。


接受的答案意味着这一点,但我想明确说明与shell内置的连接:

如前所述,export将为shell和子级提供一个变量。如果不使用export,变量将仅在shell中可用,并且只有shell内置程序可以访问它。

也就是说,

1
2
3
tango=3
env | grep tango # prints nothing, since env is a child process
set | grep tango # prints tango=3 -"type set" shows `set` is a shell builtin

这里还有另一个例子:

1
2
3
4
5
VARTEST="value of VARTEST"
#export VARTEST="value of VARTEST"
sudo env | grep -i vartest
sudo echo ${SUDO_USER} ${SUDO_UID}:${SUDO_GID}"${VARTEST}"
sudo bash -c 'echo ${SUDO_USER} ${SUDO_UID}:${SUDO_GID}"${VARTEST}"'

只有使用export vartest,vartest的值才能在sudo bash-c'…'!

有关更多示例,请参见:

  • http://mywiki.wooledge.org/subshell

  • bash-hacker.org/wiki/doku.php/scripting/processtree


Unix的两个创建者,BrianKernighan和RobPike,在他们的书《Unix编程环境》中解释了这一点。谷歌的标题,你会很容易找到一个PDF版本。

它们在第3.6节中讨论了shell变量,并重点介绍了在该节末尾使用export命令:

When you want to make the value of a variable accessible in sub-shells, the shell's export command should be used. (You might think about why there is no way to export the value of a variable from a sub-shell to its parent).


只需显示环境中的导出变量(env)与环境中的非导出变量之间的差异:

如果我这样做:

1
2
$ MYNAME=Fred
$ export OURNAME=Jim

然后只有$ourname出现在env中。变量$myname不在env中。

1
2
$ env | grep NAME
OURNAME=Jim

但是变量$myname确实存在于shell中

1
2
$ echo $MYNAME
Fred

尽管在讨论中没有明确提到,但是在从bash内部生成子shell时,不必使用export,因为所有变量都被复制到子进程中。


默认情况下,脚本中创建的变量仅对当前shell可用;子进程(子shell)将无法访问已设置或修改的值。允许子进程查看值,需要使用export命令。