如何读取进程的环境变量

53 linux process environment-variables

Linux/proc/<pid>/environ不会更新进程的环境。据我了解,该文件包含进程的初始环境。

如何读取进程的当前环境?

Jon*_*ham 48

您可以从 读取进程的初始环境/proc/<pid>/environ。

如果一个进程改变了它的环境,那么为了读取该环境,您必须拥有该进程的符号表并使用ptrace系统调用(例如通过使用gdb)从全局char **__environ变量中读取环境。没有任何其他方法可以从正在运行的 Linux 进程中获取任何变量的值。

这就是答案。现在做一些笔记。

以上假设该流程符合 POSIX,这意味着该流程使用Ref Spec 中char **__environ指定的全局变量来管理其环境。

进程的初始环境在进程堆栈上的固定长度缓冲区中传递给进程。(执行此操作的通常机制是linux//fs/exec.c:do_execve_common(...)。)由于计算出的缓冲区大小不超过初始环境所需的大小,因此您无法在不擦除现有变量或粉碎堆栈的情况下添加新变量。因此,任何允许更改进程环境的合理方案都将使用堆,其中可以分配和释放任意大小的内存,这正是 GNU libc( glibc) 为您所做的。

如果进程使用glibc,那么它是符合 POSIX 的,__environ在glibc//posix/environ.cGlibc中声明__environ它用一个指向它malloc来自进程堆的内存的指针进行初始化,然后将初始环境从堆栈复制到这个堆区域。进程每次使用该setenv函数时,都会glibc执行 arealloc来调整__environ指向以容纳新值或变量的区域的大小。(您可以使用 下载 glibc 源代码git clone git://sourceware.org/git/glibc.git glibc)。要真正理解该机制,您还必须阅读hurd//init/init.c:frob_kernel_process()(git clone git://git.sv.gnu.org/hurd/hurd.git hurd) 中的 Hurd 代码。

现在,如果新进程仅被forked,而没有后续exec覆盖堆栈,则参数和环境复制魔术在 中完成linux//kernel/fork.c:do_fork(...),其中copy_process例程调用通过调用dup_task_struct来分配新进程的堆栈alloc_thread_info_node,它使用 为新进程调用setup_thread_stack( linux//include/linux/sched.h) alloc_thread_info_node。

最后,POSIX__environ约定是用户空间约定。它与 Linux 内核中的任何内容都没有联系。您可以在不使用glibc和不使用__environ全局变量的情况下编写用户空间程序,然后根据需要管理环境变量。没有人会因为您这样做而逮捕您,但您必须编写自己的环境管理函数 ( setenv/ getenv) 和自己的包装器,sys_exec而且很可能没有人能够猜出您将更改放置到环境的位置。

  • `/proc/[pid]/` 中的许多文件似乎都有奇怪的编码(其他人可能知道是什么以及为什么)。对我来说,简单的“cat environ”就会以一种非常难以阅读的格式打印出环境变量。`猫环境| strings` 为我解决了这个问题。 (2认同)

Gil*_*il' 26

/proc/$pid/environ如果进程改变它自己的环境,它会更新。但是很多程序并不费心去改变自己的环境,因为这有点没有意义:一个程序的环境不是通过正常渠道可见的,只能通过/procand ps,甚至不是每个unix变体都有这种特性,所以应用程序不依赖在上面。

就内核而言,环境仅作为execve启动程序的系统调用的参数出现。Linux 通过 公开内存中的一个区域/proc,一些程序更新这个区域,而其他程序则不更新。特别是,我认为任何 shell 都不会更新这个区域。由于该区域具有固定大小,因此无法添加新变量或更改值的长度。

  • @Gilles:这个答案充其量具有误导性(-1)。/proc/$$/environ 中的环境是从进程的堆栈中读取的。参见 fs/proc/base.c。这是初始环境。它永远不会更新,事实上也不可能更新。libc setenv 使用的环境在堆上分配并使用堆栈中环境的内容进行初始化。如果进程调用 libc 的“fork”,则 libc 使用子进程的堆分配环境进行“sys_fork”调用。 (11认同)
  • @JonathanBen-Avraham 你是对的,初始环境没有在任何 shell 中更新。然而,该区域不仅在 Linux 下是只读的,我遇到过使用它来报告其状态的程序(通过`argv` 的状态报告更为常见,但两者都存在)。 (8认同)
  • @Nikhil 当然,是的。但是仅仅因为你在 shell 中写了 `PATH=foo` 并不意味着 shell 会修改 `*envp`。在某些 shell 中,这仅更新了内部数据结构,而更新 `*envp` 的是外部程序执行代码。例如,查看 bash 源代码中 `variables.c` 中的 `assign_in_env`。 (3认同)

Nik*_*ley 24

它在进程获取/删除其环境变量时更新。您是否有参考environ文件说明该文件未在 /proc 文件系统下的进程目录中为进程更新?

xargs --null --max-args=1 echo < /proc/self/environ
Run Code Online (Sandbox Code Playgroud)

或者

xargs --null --max-args=1 echo < /proc/<pid>/environ
Run Code Online (Sandbox Code Playgroud)

或者

ps e -p <pid>
Run Code Online (Sandbox Code Playgroud)

以上将以ps输出格式打印进程的环境变量,需要文本处理(解析/过滤)以列表形式查看环境变量。

Solaris(没有被问到,但作为参考,我会在这里发布):

/usr/ucb/ps -wwwe <pid>
Run Code Online (Sandbox Code Playgroud)

或者

pargs -e <pid> 
Run Code Online (Sandbox Code Playgroud)

编辑: /proc/pid/environ 未更新!我站着纠正。验证过程如下。但是,进程派生的子进程将继承进程环境变量,并且它在各自的 /proc/self/environ 文件中可见。(使用字符串)

在 shell 中:这里 xargs 是一个子进程,因此继承了环境变量并反映在它的/proc/self/environ文件中。

[centos@centos t]$ printenv  | grep MASK
[centos@centos t]$ export MASK=NIKHIL
[centos@centos t]$ printenv  | grep MASK
MASK=NIKHIL
[centos@centos t]$ xargs --null --max-args=1 echo < /proc/self/environ  | grep MASK
MASK=NIKHIL
[centos@centos t]$ unset MASK
[centos@centos t]$ printenv  | grep MASK
[centos@centos t]$ xargs --null --max-args=1 echo < /proc/self/environ  | grep MASK
[centos@centos t]$
Run Code Online (Sandbox Code Playgroud)

从其他会话中检查它,其中终端/会话不是设置环境变量的 shell 的子进程。

从同一主机上的另一个终端/会话进行验证:

terminal1: : 请注意,printenv 是分叉的并且是 bash 的子进程,因此它读取自己的环境文件。

[centos@centos t]$ echo $$
2610
[centos@centos t]$ export SPIDEY=NIKHIL
[centos@centos t]$ printenv | grep SPIDEY
SPIDEY=NIKHIL
[centos@centos t]$ 
Run Code Online (Sandbox Code Playgroud)

terminal2:在同一台主机上——不要在设置了上述变量的同一个 shell 中启动它,单独启动终端。

[centos@centos ~]$ echo $$
4436
[centos@centos ~]$ xargs --null --max-args=1 echo < /proc/self/environ | grep -i spidey
[centos@centos ~]$ strings -f /proc/2610/environ | grep -i spidey
[centos@centos ~]$ xargs --null --max-args=1 echo < /proc/2610/environ | grep -i spidey
[centos@centos ~]$ 
Run Code Online (Sandbox Code Playgroud)

  • 我在一个 bash 的会话 (pid xxxx) 中执行 `export foo=bar`,然后执行 `cat /proc/xxxx/environ | tr \\0 \\n` 在其他 bash 的会话中,我没有看到 `foo`。 (2认同)

fib*_*cci 14

那么下面的内容与作者的真实意图无关,但如果你真的想“阅读” /proc/<pid>/environ,你可以尝试

strings /proc/<pid>/environ
Run Code Online (Sandbox Code Playgroud)

这比cat它好。

  • 默认情况下 `strings` 仅打印长度 &gt;= 4 的字符串。像 `A=1` 这样的环境变量将被忽略。此外,空白(除了空格和制表符)也会被忽略。您可以使用“strings -n2 -w”来更改此设置。 (4认同)
  • +1 表示`字符串`。把事情简单化。 (3认同)
  • “文件”以空值终止,用换行符替换空值,正常工具再次工作(带有通常的警告),例如:`tr '\0' '\n' &lt; /proc/$$/environ | ...` (2认同)