定义带或不带导出的变量

export是什么意思?

有什么区别:

export name=value

name=value
468667 次浏览

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

export使变量可用于子进程。

那就是,

export name=value

表示变量名可用于从该shell进程运行的任何过程。如果您希望进程使用此变量,请使用export,并从该shell运行该进程。

name=value

意味着变量范围仅限于shell,并且不适用于任何其他进程。您可以将其用于(例如)循环变量、临时变量等。

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

为了说明其他答案所说的话:

$ foo="Hello, World"$ echo $fooHello, World$ bar="Goodbye"$ export foo$ bashbash-3.2$ echo $fooHello, Worldbash-3.2$ echo $bar
bash-3.2$

此答案为错误,但保留用于历史目的。

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

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

第二次编辑(又过了5年)。这个答案太奇怪了。也许我曾经有某种理由声称bash将导出的变量放入了自己的环境中,但这些原因在这里没有给出,现在已经被历史遗忘了。见将函数局部变量导出到环境

下面是另一个例子:

VARTEST="value of VARTEST"#export VARTEST="value of VARTEST"sudo env | grep -i vartestsudo echo ${SUDO_USER} ${SUDO_UID}:${SUDO_GID} "${VARTEST}"sudo bash -c 'echo ${SUDO_USER} ${SUDO_UID}:${SUDO_GID} "${VARTEST}"'

只有使用导出VARTEST,VARTEST的值才能在sudo bash-c'…'中使用!

更多示例见:

export NAME=value表示对子进程有意义的设置和变量。

NAME=value表示当前shell进程私有的临时变量或循环变量。

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

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

    $ export FOO = "bar"bash: export: `=': not a valid identifier
  • Only the exported variable (B) is seen by the subprocess:

    $ A="Alice"; export B="Bob"; echo "echo A is \$A. B is \$B" | bashA is . B is Bob
  • Changes in the subprocess do not change the main shell:

    $ export B="Bob"; echo 'B="Banana"' | bash; echo $BBob
  • Variables marked for export have values copied when the subprocess is created:

    $ 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")' | bashSubprocess 1 has B=BobSubprocess 2 has B=Banana[1]+  Done         echo '(sleep 30; echo "Subprocess 1 has B=$B")' | bash
  • Only exported variables become part of the environment (man environ):

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

So, now it should be as clear as is the summer's sun! Thanks to Brain Agnew, alexp, and William Prusell.

虽然在讨论中没有明确提到,但从bash内部生成子shell时没有必要使用导出,因为所有变量都复制到子进程中。

只是为了显示导出变量在环境中(env)和未导出变量不在环境中之间的区别:

如果我这样做:

$ MYNAME=Fred$ export OURNAME=Jim

然后只有$OURNAME出现在环境中。变量$MYNAME不在环境中。

$ env | grep NAMEOURNAME=Jim

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

$ echo $MYNAMEFred

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

  • Sub外壳可以访问父级的所有变量,无论它们的导出状态如何。
  • Sub过程只有看到导出的变量。

这两个构造中的共同点是都不能将变量传递回父shell。

$ noexport=noexport; export export=export; (echo subshell: $noexport $export; subshell=subshell); bash -c 'echo subprocess: $noexport $export; subprocess=subprocess'; echo parent: $subshell $subprocesssubshell: noexport exportsubprocess: exportparent:

还有一个混淆的根源:有些人认为“forked”子进程是看不到非导出变量的子进程。通常forked()的后面紧跟着exec(),这就是为什么fork()似乎是要查找的东西,而实际上它是exec()。你可以先使用exec命令运行没有fork()ing的命令,并且由该方法启动的进程也无法访问未导出的变量:

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

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

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

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

程序可以通过此UNIX API访问其环境变量:

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

进程也从父进程继承环境变量。操作系统负责在创建子进程时创建所有“envars”的副本。

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

export是为Bash设置环境变量的Bash命令。使用此命令设置的所有变量都将被此Bash将创建的所有进程继承。

更多关于Bash环境

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

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

更多关于外壳参数(包括变量)

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

任何简单命令或函数的环境都可以增强暂时用参数赋值作为前缀,如所述在外壳参数中。这些赋值语句仅影响该命令所看到的环境。


总结一下:

  • export用于设置操作系统中的环境变量。该变量将适用于当前Bash进程创建的所有子进程。
  • bash变量表示法(name=value)用于设置仅对bash当前进程可用的局部变量
  • 前缀另一个命令的Bash变量表示法仅为该命令的范围创建环境变量。

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

$ K=1$ export K$ K=2$ bash -c 'echo ${K-unset}'2$ export -n K$ bash -c 'echo ${K-unset}'unset

UNIX的两位创建者Brian Kernighan和Rob Pike在他们的书“UNIX编程环境”中解释了这一点。谷歌搜索标题,您可以轻松找到PDF版本。

他们在3.6节中解决了shell变量,并在该节末尾重点介绍了export命令的使用:

当您想使变量的值在子shell中可访问时,应该使用shell的导出命令。(您可能会想为什么无法将变量的值从子shell导出到其父级)。

接受的答案暗示了这一点,但我想明确说明与shell内置的连接:

如前所述,export将使shell和子级都可以使用一个变量。如果export没有使用,该变量将仅在shell中可用,并且只有shell内置可以访问它。

那就是,

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

作为现有答案的另一个推论,让我们重新表述问题陈述。

“我应该export”的答案与“您的后续代码是否运行隐含访问此变量的命令?”的答案相同。

对于一个正确记录的标准实用程序,这个问题的答案可以在实用程序手册页的环境部分找到。因此,例如,#0手册页提到GIT_PAGER控制哪个实用程序用于浏览git的多页输出。因此,

# XXX FIXME: buggybranch="main"GIT_PAGER="less"git log -n 25 --oneline "$branch"git log "$branch"

将无法正常工作,因为您没有export GIT_PAGER。(当然,如果您的系统已经将变量声明为导出到其他地方,则bug是不可重现的。)

我们的明确指的是变量$branch,而git的程序代码并没有在任何地方引用系统变量branch(它的名称是用小写的这一事实也表明了这一点;但是许多初学者也错误地将大写用于他们的私有变量!参见正确的Bash和外壳脚本变量大写进行讨论)所以没有理由export branch

正确的代码看起来像

branch="main"export GIT_PAGER="less"git log -n 25 --oneline "$branch"git log -p "$branch"

(或者等效地,您可以显式地将git的每次调用与临时分配作为前缀

branch="main"GIT_PAGER="less" git log -n 25 --oneline "$branch"GIT_PAGER="less" git log -p "$branch"

如果不明显,外壳脚本语法

var=value command arguments

临时设置varvalue的执行期间

command arguments

并将其导出到command子进程,然后将其恢复到以前的值,该值可能是未定义的,或者使用不同的-可能为空的-值定义,如果这是之前的值,则未导出。)

对于内部工具、临时工具或文档记录较差的工具,您只需知道它们是否静默地检查其环境。这在实践中很少重要,除非有一些特定的用例,例如将密码或身份验证令牌或其他秘密信息传递给在某种容器或隔离环境中运行的进程。

如果你真的需要知道并访问源代码,请查找使用getenv系统调用的代码(或者在Windows上,我很抱歉,像getenv_sw_getenv等变体)。对于某些脚本语言(如Perl或Ruby),请查找ENV。对于Python,请查找os.environ(但也要注意,例如from os import environ as foo意味着foo现在是os.environ的别名)。在Node中,请查找process.env。对于C和相关语言,请查找envp(但这只是在getenv_s1和getenv_s2之后调用getenv_s0的可选第三个参数的约定;该语言允许您随意调用它们)。对于shell脚本(如上所述),也许要查找具有大写或偶尔混合大写名称的变量,或者使用实用程序getenv_s3。许多非正式脚本通常在脚本开头附近有未记录但可发现的赋值;特别是,请查找getenv_s4默认赋值getenv_s5。

对于一个简短的演示,这里有一个外壳脚本,它调用一个Python脚本来查找$NICKNAME,如果它未设置,则回退到默认值。

#!/bin/shNICKNAME="Handsome Guy"demo () {python3 <<\____from os import environ as envprint("Hello, %s" % env.get("NICKNAME", "Anonymous Coward"))____}demo# prints "Hello, Anonymous Coward"# Fix: forgot exportexport NICKNAMEdemo# prints "Hello, Handsome Guy"

作为另一个不相关的评论,让我重申您只需要export一个变量一次。许多初学者喜欢的货物崇拜代码

# XXX FIXME: redundant exportsexport PATH="$HOME/bin:$PATH"export PATH="/opt/acme/bin:$PATH"

但通常情况下,您的操作系统已经将PATH声明为导出,因此更好地编写

PATH="$HOME/bin:$PATH"PATH="/opt/acme/bin:$PATH"

或者重构成类似

for p in "$HOME/bin" "/opt/acme/bin"docase :$PATH: in*:"$p":*) ;;*) PATH="$p:$PATH";;esacdone# Avoid polluting the variable namespace of your interactive shellunset p

这避免了在PATH中添加重复的条目。