答案1
您的假设是 shell 变量是在环境中。这是不正确的。该export
命令定义了环境中的名称。因此:
a=1 b=2
export b
结果当前壳知道它$a
会扩展到 1 和$b
2,但子进程不会知道任何事情,a
因为它不是环境的一部分(即使在当前 shell 中)。
一些有用的工具:
set
:用于查看当前 shell 的参数(导出或未导出)set -k
: 套指定的参数在环境中。考虑f() { set -k; env; }; f a=1
set -a
:告诉 shell 将设置的任何名称放入环境中。就像放在export
每项作业之前一样。对于.env
文件很有用,如set -a; . .env; set +a
.export
:告诉 shell 在环境中输入一个名称。导出和赋值是两种完全不同的操作。env
:作为外部命令,env
只能告诉你有关遗传环境,因此,它对于健全性检查很有用。env -i
:对于在启动子进程之前清除环境很有用。
替代方案export
:
name=val command
# 命令之前的赋值将该名称导出到命令。declare/local -x name
# 导出名称,当您想避免将名称暴露给外部作用域时,在 shell 函数中特别有用。set -a
# 导出以下每个作业。
动机
那么为什么 shell 需要有自己不同的变量和环境呢?我确信有一些历史原因,但我认为主要原因是范围界定。该环境适用于子进程,但是您可以在 shell 中执行许多操作,而无需分叉子进程。假设你循环:
for i in {0..50}; do
somecommand
done
somecommand
为什么要通过包含来浪费内存i
,使其环境比需要的更大?如果您在 shell 中选择的变量名称恰好意味着程序无意的含义怎么办? (我个人最喜欢的包括DEBUG
和VERBOSE
。这些名称随处可见,但命名空间很少。)
如果不是shell,那是什么环境呢?
有时要了解 Unix 行为,您必须查看系统调用,这是与内核和操作系统交互的基本 API。在这里,我们正在研究exec
调用系列,这是 shell 在创建子进程时使用的调用系列。这是来自联机帮助页exec(3)
(强调我的):
execle()
和函数execvpe()
允许调用者通过参数 envp 指定执行程序的环境。 envp 参数是指向以 NULL 结尾的字符串的指针数组,并且必须以 NULL 指针结尾。其他函数从调用进程中的外部变量 environ 获取新进程映像的环境。
因此,在 shell 中编写export somename
相当于将名称复制到environ
C 中的全局字典中。但是somename
在不导出它的情况下进行赋值就像在 C 中对其进行赋值一样,而不将其复制到变量中environ
。
答案2
shell 变量和环境变量之间是有区别的。如果定义 shell 变量而不对其export
进行 ing,则它不会添加到进程环境中,因此不会继承到其子进程。
使用export
您告诉 shell 将 shell 变量添加到环境中。您可以使用测试它printenv
(它只是将其环境打印到stdout
,因为它是一个子进程,您可以看到export
ing 变量的效果):
#!/bin/sh
MYVAR="my cool variable"
echo "Without export:"
printenv | grep MYVAR
echo "With export:"
export MYVAR
printenv | grep MYVAR
答案3
变量一旦导出,就是环境的一部分。PATH
在 shell 本身中导出,而自定义变量可以根据需要导出。使用一些设置代码:
$ cat subshell.sh
#!/usr/bin/env bash
declare | grep -e '^PATH=' -e '^foo='
比较
$ cat test.sh
#!/usr/bin/env bash
export PATH=/bin
export foo=bar
declare | grep -e '^PATH=' -e '^foo='
./subshell.sh
$ ./test.sh
PATH=/bin
foo=bar
PATH=/bin
foo=bar
和
$ cat test2.sh
#!/usr/bin/env bash
PATH=/bin
foo=bar
declare | grep -e '^PATH=' -e '^foo='
./subshell.sh
$ ./test2.sh
PATH=/bin
foo=bar
PATH=/bin
由于foo
不是由 shell 导出的,并且test2.sh
从未导出它,因此它不是subshell.sh
上次运行环境的一部分。
答案4
总之:
- 进程确实从其父进程继承环境变量
- 在 bash 中, $a=1 创建一个“普通”变量,而不是环境变量
- export a=1 为终端的进程创建一个环境变量(还有其他方法可以做到)
- 还有其他方法可以将环境数据传递给子进程