试图了解 Linux(具体来说是 Ubuntu 13.04)中环境的行为,我发现在不同的上下文中使用或定义设置环境变量的不同情况。例如,如果我检查 ,locale
我会得到:
$ locale
LANG=en_US.UTF-8
LANGUAGE=es_ES:es_HN:es_EC:en
LC_CTYPE="en_US.UTF-8"
LC_NUMERIC=es_ES.UTF-8
// more output
但是,如果我发现,例如,LC_CTYPE
使用env | grep "LC_CTYPE"
,它不会发送任何输出。一般来说,locale
显示了 13 个LC_*
变量,但env
只有 9 个:
$ locale | grep "LC_*" | wc -l
13
$ env | grep "LC_*" | wc -l
9
具有不同“性质”的其他变量是PS1
。例如:
$ env | grep "PS1" # No output, but...
$ set | grep "PS1" | head -n 1
PS1=$'\\[\\033[1;33m\\][\\t][\\W]\342\230\233\\[\\033[0m\\] '
当然,PS1
在我当前的环境中是一个定义明确的变量,因为我看到我的提示符相应地发生了变化。
其他方式观看其他上下文中的环境变量是通过strace
.它是一个程序,可让您查看执行程序时发生的情况。样本:
$ strace -v ./a.out # a.out is a common Hello World, made in C.
execve("./a.out", ["./a.out"], ["LC_PAPER=es_ES.UTF-8", ...]) = 0
brk(0)
access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory)
# etc, etc
write(1, "Hello World\n", 12Hello World
) = 12
exit_group(0) = ?
shell 在执行程序时所做的第一件事是调用execve
,它确实调用了程序。它的第一个参数是被调用的程序,第二个参数是argv
被调用程序的参数,第三个参数是环境变量。
例如,在第三个参数中,不会出现PS1
或LC_TYPE
。
一般来说,出现在env
或set
出现在发送到的环境变量列表中的变量execve
。某些locale
变量出现在env
or中set
,但其他变量则不出现(LC_TYPE
、LC_COLLATE
andLC_MESSAGE
以及LC_ALL
but 为空值)。最后,其他变量env
虽然具有可见的效果( ),但并未定义PS1
,如 所反映的set
。
这里发生了什么?env
,set
(不带参数),locale
(显然仅考虑区域设置变量)之间有什么区别?
答案1
这里的主要问题——这解释了为什么,例如,$PS1
没有报告env
——是env
来自一个报告非交互式环境。进程是从您的分支执行的交互的shell,但它们的环境设置方式有一个微妙之处:它实际上是通过为所有exec()
进程设置的本机 C 级外部变量继承的(请参阅 参考资料man environ
)。这是一个例子:
#include <stdio.h>
extern char **environ;
int main (void) {
int i;
for (i = 0; environ[i] != NULL; i++) {
printf("%s\n", environ[i]);
}
return 0;
}
有趣的是,如果你编译并运行它,你会发现 的内容与**environ
报告的内容完全匹配env
:
$ gcc test.c
$ ./a.out > aout.txt
$ env > env.txt
$ diff env.txt aout.txt
68c68
< _=/bin/env
---
> _=./a.out
唯一的区别是可执行文件的名称。那么它**environ
来自哪里,为什么它不包含,例如$PS1
?
基本的解释是,进程总是作为其他进程的子进程创建,并且继承**environ
,但PS1
从来不是它的一部分。启动时,shell 可以从标准位置获取变量,这些位置根据 shell 是否是交互式的而有所不同;看召唤在man bash
。这其中的一个方面是:
PS1 已设置 [...]如果 bash 是交互式的,允许 shell 脚本或启动文件来测试此状态。
现在,请注意/etc/bashrc
这样的事情:
# are we an interactive shell?
if [ "$PS1" ]; then
这是您实际(花式)提示的设置位置,并且它和 的初始值都$PS1
没有被export
编辑过。初始值是由 shell 在调用时创建的,因为它是交互式的,然后它获取该文件 - 但PS1
没有放入**environ
.如果执行以下命令,您可以看到这一点:
#!/bin/sh
echo $PS1
什么也没有——即使你echo $PS1
在交互式 shell 中它已经被定义了。这是因为**environ
执行的#!/bin/sh
与父交互式 shell 的相同,但不包含PS1
.这意味着每个 shell 使用一个单独的全局变量内部表,但最初填充的是**environ
(这很令人困惑,因为这意味着**environ
不包括许多称为环境变量)。
的内容**environ
位于 中/proc/[PID]/environ
,如果您检查当前交互式 shell 中的内容,cat /proc/$BASHPID/environ
您会发现PS1
不在那里。
但是东西是如何进入“环境”的呢?
简单的答案是,通过系统调用。例如,如果我们将一些内容放入前面的示例 C 程序中:
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
extern char **environ;
int main (void) {
int i;
if (putenv("MYFOO=whatbar?")) {
fprintf(stderr, "putenv() failed: %s\n", strerror(errno));
exit(1);
}
for (i = 0; environ[i] != NULL; i++) {
printf("%s\n", environ[i]);
}
return 0;
}
MYFOO=whatbar?
将出现在输出中(请参阅man putenv
)。由于 shell 通过fork()
ing(复制父进程的内存堆栈)然后调用execv()
(传递复制的**environ
)来创建进程,因此我们可以看到一种将环境变量传递export
给子进程的机制。
如果您将 a 放入fork()
该示例中,您将看到情况就是如此,并且(重申一下),这个分叉和潜在执行的过程是创建子进程并**environ
从其祖先继承的方式。exec
调用替换了过程映像,但根据man execv
和man environ
(注意。前者的某些版本不引用此),**environ
由系统传递。
/usr/bin/env
这是一个通过MYFOO=whatbar?
导出的字面 fork 和 exec putenv()
:
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
extern char **environ;
int main (void) {
pid_t pid;
if (putenv("MYFOO=whatbar?")) {
fprintf(stderr, "putenv() failed: %s\n", strerror(errno));
exit(1);
}
pid_t pid = fork();
if (!pid) execl("/usr/bin/env", "env", NULL);
return 0;
}
那么“环境”之外的东西在哪里呢?
它是特定 shell 实例的私有数据。 Bash 将通过不带任何参数的方式向您显示这个 + 继承的环境内容set
。请注意,此输出还包括源函数。
但是,如果我发现,例如,使用 env | LC_CTYPE grep "LC_CTYPE",它不发送任何输出。一般来说,locale 显示了 13 个 LC_* 变量,而 env 仅显示了 9 个:
LC_
我根本没有从env
(只是LANG
)得到任何变量,但从 得到了 13 个变量locale
。我认为这些是由locale
调用设置的变量,而不是导出的;您从中得到任何信息的事实env
可能反映了某处某些配置中的天真错误。
答案2
shell 知道两种类型的变量:
仅 shell(和子 shell)知道的“内部”变量
execve
导出的变量,即被看到的“官方”变量env
。 shell 内置命令export
向您显示导出的变量。
如果你执行
export PS1
并重复
env | grep "PS1"
然后你就看到了。变量可以在创建期间导出(export foo=bar
而不是foo=bar
),可以在创建或修改时自动导出(set -a
),可以稍后导出(var=foo; ...; export var
),并且可以“不导出”(export -n var
)。
如果 shell 创建“真正的”子 shell(通过、a|b
等),它会保留几个非导出变量以避免混乱。(a;b)
$(a)
答案3
该命令的输出locale
不是当前环境中的环境变量列表。它显示该进程的有效区域设置(部分受到某些环境变量的影响),并以key=value
与该env
命令使用的相同格式呈现。
您可以在此处查看 eglibc 命令实现的源代码locale
:
http://www.eglibc.org/cgi-bin/viewvc.cgi/branches/eglibc-2_19/libc/locale/programs/locale.c?view=markup