但是东西是如何进入“环境”的呢?

但是东西是如何进入“环境”的呢?

试图了解 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被调用程序的参数,第三个参数是环境变量。

例如,在第三个参数中,不会出现PS1LC_TYPE

一般来说,出现在envset出现在发送到的环境变量列表中的变量execve。某些locale变量出现在envor中set,但其他变量则不出现(LC_TYPELC_COLLATEandLC_MESSAGE以及LC_ALLbut 为空值)。最后,其他变量env虽然具有可见的效果( ),但并未定义PS1,如 所反映的set

这里发生了什么?envset(不带参数),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 execvman 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 知道两种类型的变量:

  1. 仅 shell(和子 shell)知道的“内部”变量

  2. 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 命令实现的源代码localehttp://www.eglibc.org/cgi-bin/viewvc.cgi/branches/eglibc-2_19/libc/locale/programs/locale.c?view=markup

相关内容