ChatGPT解决这个技术问题 Extra ChatGPT

定义带或不带导出的变量

export 有什么用?

有什么区别:

export name=value

name=value

B
Brian Agnew

export 使变量可用于子流程。

那是,

export name=value

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

name=value

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

请务必注意,导出变量不会使其可用于父进程。也就是说,在衍生进程中指定和导出变量不会使其在启动它的进程中可用。


特别是 export 使变量可以通过环境对子进程可用。
我还要补充一点,如果导出位于您“来源”的文件中(例如 .filename),那么它也会将其导出到您的工作环境中。
@rogerdpack 如果不导出就不能这样做吗?猫 > 等等 \na=hi \n 。废话;回声$a;为我输出“嗨”。
很好,即使没有出口它也能工作。所以我想在采购文件时,如果你使用 export 它会反映在子进程中,如果你不这样做只会影响本地 bash 环境......
这有一个极端情况。 name=value command 确实 使变量在子流程 command 中可用。
4
4wk_

为了说明其他答案在说什么:

$ 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$ 

al$ foobar="Whatever" bash 的另一个示例
以防万一有人想在 Bash 中用数组尝试这个(就像我做的那样......)然后单挑:it can't be done
4
4wk_

这个答案是错误的,但出于历史目的而保留。请参阅下面的第二次编辑。

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

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

第二次编辑(又过了 5 年)。 这个答案很奇怪。也许我曾经有一些理由声称 bash 将导出的变量放入它自己的环境中,但是这些原因在这里没有给出,现在已经被历史遗忘了。请参阅Exporting a function local variable to the environment


请注意,这并不完全正确。在 bash 中,export 确实将变量添加到当前 shell 的环境中,但 dash 不是这种情况。在我看来,将变量添加到当前 shell 的环境中是实现 export 语义的最简单方法,但这种行为不是强制性的。
我不确定 dash 与此有什么关系。原始发帖人专门询问bash
该问题被标记为 bash,但同样适用于任何 bourne-shell 变体。过于具体并提供仅适用于 bash 的答案是一种大恶。
bash 是 shell 的 jQuery。
export makes the variable available to subshells, and that is correct 这是一个非常混乱的术语用法。子shell 不需要 export 来继承变量。子流程可以。
l
lazaruslarue

有人说生成子shell时不必在bash中导出,而其他人则完全相反。重要的是要注意子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()s 之后紧跟 exec()s,这就是为什么看起来 fork() 是要寻找的东西,而实际上它是 exec()。您可以在没有 fork() 的情况下首先使用 exec 命令运行命令,并且由此方法启动的进程也将无法访问未导出的变量:

$ 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的循环; OTOH 管道确实(总是用于最后一个以外的部分,有时用于最后一个,具体取决于您的外壳、版本和选项)。后台 (&) 还会创建一个子外壳。
这些 var=asdf bash -c 'echo $var'var=asdf exec bash -c 'echo $var' 怎么样?输出为 asdf。如果放在变量定义之后,; 会有所不同。会有什么解释?看起来 var(没有 ;)以某种方式与生成的子进程有关,因为原始 shell 与它无关。 echo $var 如果在第二行执行,则不打印任何内容。但是一排 var=asdf bash -c 'echo $var'; echo $var 给出 asdf\nasdf
@4xy 那是完全不同的情况; var=value command 在命令 command 的持续时间内,在该命令的环境中将变量 var 设置为值 value。这有点类似于 env 命令的作用。
C
Charles Merriam

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

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

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

一个常见的错误是在等号周围放置一个空格: $ export FOO = "bar" bash: export: `=': not a valid identifier

子进程只能看到导出的变量(B):$ A="Alice";出口 B="鲍勃"; echo "回声 A 是 \$A。B 是 \$B" |重击 A 是 . B 是鲍勃

子进程的变化不会改变主shell: $ export B="Bob";回声'B =“香蕉”' |重击;回声 $B 鲍勃

标记为导出的变量在创建子流程时复制了值: $ export B="Bob"; echo '(sleep 30; echo "子进程 1 有 B=$B")' |重击 & [1] 3306 $ B="香蕉"; echo '(sleep 30; echo "子进程 2 有 B=$B")' | bash 子进程 1 有 B=Bob 子进程 2 有 B=Banana [1]+ 完成 echo '(sleep 30; echo "Subprocess 1 has B=$B")' |重击

只有导出的变量成为环境的一部分(man environ): $ ALICE="Alice";出口鲍勃=“鲍勃”;环境 | grep "爱丽丝\|鲍勃" BOB=鲍勃

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


M
Mateusz Piotrowski

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

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

谢谢,这正是我正在寻找的信息,因为我看到了一个使用环境变量的脚本,然后用新值“重新导出”它们,我想知道是否有必要。
J
John T

export 将使变量可用于从当前 shell 派生的所有 shell。


嗨约翰,你知道这个出口的位置是否重要吗?我应该把它放在makefile的底部还是任何地方都可以?
Makefile 中的@leolehu export 通常是一个单独的问题。 Quick experimentation 表明,可以预见的是,export 的位置并不重要。 (可以预测,因为 make 将解析和处理整个 Makefile,然后然后尝试解析依赖关系以确定要运行的目标。)
p
progalgo

您可能已经知道,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 创建的所有进程继承。

更多关于Environment in Bash

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

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

详细了解 Shell Parameters(包括变量)

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

任何简单命令或函数的环境都可以通过在其前面加上参数分配来临时扩充,如 Shell 参数中所述。这些赋值语句只影响该命令看到的环境。

总结一下:

export 用于在操作系统中设置环境变量。此变量将可用于当前 Bash 进程创建的所有子进程。

bash 变量表示法 (name=value) 用于设置仅对当前 bash 进程可用的局部变量

为另一个命令添加前缀的 Bash 变量表示法仅为该命令的范围创建环境变量。


bash var 不支持与 Python 一样多的类型,但确实有字符串、整数和两种数组('indexed'/traditional 和 'associative',类似于 awk 数组、perl hash 或 Python dict)。其他贝壳有所不同;只有字符串是可移植的。
@dave_thompson_085 - 实际上,所有这些都存储为字符串数组,并在必要时自动转换为算术等。像 A="string" 这样的公共变量实际上与 A[0]="string" 相同。事实上,在说出 A="string" 之后,您可以使用 A[1]="string2"A+=(string3 string4 "string 5 is longer") 然后 echo "${A[@]}" 将更多字符串连接到 1 字符串数组以打印它们。请注意,它需要将数组提供给 printf 命令之类的东西,以便在字符串之间获得某种分隔符,因为默认值是空格,而 string5 包含空格。
@DocSalvager:export a b; a=this; b[0]=that; env | grep ^[ab] 不相同。在 C/C++/Java 中,floatdouble某些 情况下可以互换,但它们仍然是不同的类型。
f
flow2k

accepted answer 暗示了这一点,但我想明确说明与 shell 内置函数的连接:

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

那是,

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

D
Dan Carter

UNIX 的两位创建者 Brian Kernighan 和 Rob Pike 在他们的《UNIX 编程环境》一书中对此进行了解释。谷歌搜索标题,你会很容易找到一个 pdf 版本。

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

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


佚名

这是另一个例子:

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-hackers.org/wiki/doku.php/scripting/processtree


W
Will

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

如果我这样做:

$ MYNAME=Fred
$ export OURNAME=Jim

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

$ env | grep NAME
OURNAME=Jim

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

$ echo $MYNAME
Fred

嗨 Will,我可以在变量声明之前导出变量吗?比如导出 OURNAME 然后 OURNAME=Jim?
@leoleohu 如果您在分配 OURNAME 之前导出它,您只需导出一个空字符串。
@ingernet即使您在分配变量之前导出变量,在调用子进程之前分配给该变量的任何值都将被子进程看到。但是一旦子进程被调用,在父进程中对导出变量所做的任何更新都不会被子进程看到,这是因为变量是在进程“exec”调用期间按值复制的
A
Amjad

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


S
Scott

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


请解释一下,因为您所说的似乎与上述示例的答案直接矛盾。
如果您不希望全局导出变量但仅可用于子流程,这是正确的方法!谢谢你。
@MikeLippert Scott 所说的子shell 是由进程替换 $() 或 `` 创建的子shell,由括号中的命令创建的子shell (command1; command2) 等自动继承所有父shell的变量,即使它们没有被导出。但是调用的子进程或脚本不会看到所有的 shell 变量,除非它们被导出。这是主要区别之一,经常被误解
@Pavan 啊,现在这很有帮助。因为通过调用新的 bash 进程创建的子 shell 不是他的意思,只会接收导出的变量。这就是我多年前问这个问题时的想法。
t
tripleee

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

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

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

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

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

我们明确引用了变量 $branch,而 git 程序代码在任何地方都没有引用系统变量 branch(正如它的名称写在小写;但许多初学者也错误地使用大写作为私有变量!参见 Correct Bash and shell script variable capitalization 进行讨论),因此没有理由使用 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"

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

var=value command arguments

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

command arguments

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

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

如果您确实需要了解并有权访问源代码,请查找使用 getenv 系统调用的代码(或在 Windows 上,我表示哀悼,getenv_sw_getenv 等变体)。对于某些脚本语言(例如 Perl 或 Ruby),请查找 ENV。对于 Python,查找 os.environ(但也请注意,例如 from os import environ as foo 意味着 foo 现在是 os.environ 的别名)。在节点中,查找 process.env。对于 C 和相关语言,请查找 envp(但这只是在 argcargv 之后调用 main 的可选第三个参数的约定;该语言允许您随意调用它们) .对于 shell 脚本(如上所述),也许寻找具有大写或偶尔混合大小写名称的变量,或者使用实用程序 env。许多非正式脚本通常在脚本开头附近有未记录但可发现的任务;特别是,寻找 ?= 默认分配 parameter expansion

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

#!/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"

作为另一个切题的话,让我重申一下,您只需要 export 一个变量一次。许多初学者货物崇拜代码喜欢

# XXX FIXME: redundant exports
export 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"
do
    case :$PATH: in
     *:"$p":*) ;;
     *) PATH="$p:$PATH";;
    esac
done
# Avoid polluting the variable namespace of your interactive shell
unset p

这样可以避免向您的 PATH 添加重复条目。