出口是干什么用的?

有什么区别:

export name=value

and

name=value

Export将使该变量对当前shell派生的所有shell可用。


导出使子流程可以使用该变量。

也就是说,

export name=value

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

name=value

意味着变量范围被限制在shell中,并且对任何其他进程都不可用。你可以将它用于(比如说)循环变量、临时变量等。

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


为了说明其他答案的意思:

$ 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年视角): 这是一个愚蠢的回答。“导出”的目的是使变量“处于随后执行的命令的环境中”,无论这些命令是子shell还是子进程。简单的实现是简单地将变量放在shell环境中,但这将使无法实现export -p。

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


这里还有一个例子:

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,才能在sudo bash -c `…`中使用VARTEST的值!

更多的例子见:

http://mywiki.wooledge.org/SubShell bash-hackers.org/wiki/doku.php/scripting/processtree


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

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

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

A common error is to place a space around the equal sign: $ 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" | bash A is . B is Bob Changes in the subprocess do not change the main shell: $ export B="Bob"; echo 'B="Banana"' | bash; echo $B Bob 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")' | bash Subprocess 1 has B=Bob Subprocess 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

所以,现在它应该像夏天的太阳一样清晰了!感谢Brain Agnew, alexp和William Prusell。


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


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

如果我这样做:

$ MYNAME=Fred
$ export OURNAME=Jim

那么只有$OURNAME出现在环境中。变量$MYNAME不在环境中。

$ env | grep NAME
OURNAME=Jim

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

$ echo $MYNAME
Fred

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

子shell将可以访问来自父变量的所有变量,而不管它们的导出状态如何。 子进程只能看到导出的变量。

这两种构造的共同之处在于它们都不能将变量传递回父shell。

$ 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()。你可以先用exec命令运行没有fork()ing的命令,由这个方法启动的进程也不能访问未导出的变量:

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

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


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


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

程序可以通过以下UNIX API访问它的环境变量:

Char *getenv(const Char *name); Int setenv(const char *名称,const char *值,Int重写); Int unsetenv(const char *name);

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

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

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

更多关于Bash中的环境

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

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

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

另外值得注意的是,根据Bash参考手册:

任何简单命令或函数的环境都可以扩充 临时用参数赋值作为前缀,如前所述 在Shell Parameters中。这些赋值语句只影响 该命令所看到的环境。


总结一下:

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


需要注意的是,您可以导出一个变量,然后更改其值。变量更改后的值将对子进程可用。一旦为一个变量设置了export,您必须执行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的export命令。(您可能会想为什么没有办法将变量的值从子shell导出到父shell)。


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

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

也就是说,

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

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

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

对于正确记录的标准实用程序,可以在该实用程序手册页的ENVIRONMENT部分中找到答案。例如,git手册页提到GIT_PAGER控制使用哪个实用程序浏览来自git的多页输出。因此,

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

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

我们显式地引用了变量$branch,而git程序代码并没有在任何地方引用系统变量分支(它的名字是小写的,这也表明了这一点;但是许多初学者也错误地使用大写字母作为私有变量!有关讨论,请参阅正确的Bash和shell脚本变量大写),因此没有理由导出分支。

正确的代码应该是这样的

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"

如果不是很明显,shell脚本语法

var=value command arguments

的执行期间临时将var设置为value

command arguments

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

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

If you really need to know, and have access to the source code, look for code which uses the getenv system call (or on Windows, with my condolences, variations like getenv_s, w_getenv, etc). For some scripting languages (such as Perl or Ruby), look for ENV. For Python, look for os.environ (but notice also that e.g. from os import environ as foo means that foo is now an alias for os.environ). In Node, look for process.env. For C and related languages, look for envp (but this is just a convention for what to call the optional third argument to main, after argc and argv; the language lets you call them anything you like). For shell scripts (as briefly mentioned above), perhaps look for variables with uppercase or occasionally mixed-case names, or usage of the utility env. Many informal scripts have undocumented but discoverable assignments usually near the beginning of the script; in particular, look for the ?= default assignment parameter expansion.

作为一个简短的演示,下面是一个shell脚本,它调用一个Python脚本,该脚本查找$NICKNAME,如果未设置则返回到默认值。

#!/bin/sh
NICKNAME="Handsome Guy"
demo () {
    python3 <<\____
from os import environ as env
print("Hello, %s" % env.get("NICKNAME", "Anonymous Coward"))
____
}
demo
# prints "Hello, Anonymous Coward"
# Fix: forgot export
export NICKNAME
demo
# prints "Hello, Handsome Guy"

作为另一个切题的评论,让我重申一下,您只需要导出一个变量一次。很多初学者都喜欢货邪代码

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

但通常情况下,您的操作系统已经将PATH声明为export,因此这是更好的编写方式

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

或者重构成

for p in "$HOME/bin" "/opt/acme/bin"
do
    case :$PATH: in
     *:"$p":*) ;;
     *) PATH="$p:$PATH";;
    esac
done
# Avoid polluting the variable namespace of your interactive shell
unset p

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