66

Linux's /proc/<pid>/environ does not update a process's environment. As I understand it, the file contains the initial environment of the process.

How can I read a process's current environment?

  • 4
    ps e -ww -p <PID> – GypsyCosmonaut Feb 04 '22 at 10:33
  • 1
    possible shortest answer: tr '\0' '\n' < /proc/$pid/environ(based on comment https://unix.stackexchange.com/questions/29128/how-to-read-environment-variables-of-a-process#comment938505_389418) – quant2016 Nov 01 '23 at 11:44
  • Both of the above suggestions still only report the initial environment of the process, not the current as this question requests. – NotTheDr01ds Jan 13 '24 at 19:45

7 Answers7

60

You can read the initial environment of a process from /proc/<pid>/environ.

If a process changes its environment, then in order to read the environment you must have the symbol table for the process and use the ptrace system call (for example by using gdb) to read the environment from the global char **__environ variable. There isn't any other way to get the value of any variable from a running Linux process.

That's the answer. Now for some notes.

The above assumes that the process is POSIX compliant, meaning that the process manages its environment using a global variable char **__environ as specified in the Ref Spec.

The initial environment for a process is passed to the process in a fixed-length buffer on the process's stack. (The usual mechanism that does this is linux//fs/exec.c:do_execve_common(...).) Since the size of the buffer is calculated to be no more than the size required for the initial environment, you can't add new variables without erasing existing variables or smashing the stack. So, any reasonable scheme to allow changes in a process's environment would use the heap, where memory in arbitrary sizes can be allocated and freed, which is exactly what GNU libc (glibc) does for you.

If the process uses glibc, then it is POSIX compliant, with __environ being declared in glibc//posix/environ.c Glibc initializes __environ with a pointer to memory that it mallocs from the process's heap, then copies the initial environment from the stack into this heap area. Each time the process uses the setenv function, glibc does a realloc to adjust the size of the area that __environ points to to accommodate the new value or variable. (You can download the glibc source code with git clone git://sourceware.org/git/glibc.git glibc). To really understand the mechanism you will also have to read the Hurd code in hurd//init/init.c:frob_kernel_process() (git clone git://git.sv.gnu.org/hurd/hurd.git hurd).

Now if the new process is only forked, without a subsequent exec overwriting the stack, then the argument and environment copying magic is done in linux//kernel/fork.c:do_fork(...), where the copy_process routine calls dup_task_struct that allocates the new process's stack by calling alloc_thread_info_node, which calls setup_thread_stack (linux//include/linux/sched.h) for the new process using alloc_thread_info_node.

Finally, the POSIX __environ convention is a user-space convention. It has no connection with anything in the Linux kernel. You can write a userspace program without using glibc and without the __environ global and then manage the environment variables however you like. No one will arrest you for doing this but you will have to write your own environment management functions (setenv/getenv) and your own wrappers for sys_exec and it is likely that no one will be able to guess where you put the changes to your environment.

Toby Speight
  • 8,678
  • 2
    Many of the files in /proc/[pid]/ appear to have a weird encoding (someone else may know what and why). For me, simply cat environ would print out the environment variables in a really hard to read format.

    cat environ | strings solved this for me.

    – retrohacker Oct 27 '18 at 18:02
  • 1
    @retrohacker This gives a more robust solution: https://askubuntu.com/questions/978711/how-do-i-split-a-proc-environ-file-in-separate-lines – Frank Kusters Jun 11 '19 at 14:59
  • @retrohacker UUOC. – Tripp Kinetics Dec 23 '20 at 20:56
38

It is updated as and when the process acquires/deletes its environment variables. Do you have a reference which states the environ file is not updated for the process in its process directory under /proc filesystem?

xargs --null --max-args=1 echo < /proc/self/environ

or

xargs --null --max-args=1 echo < /proc/<pid>/environ

or

ps e -p <pid>

The above will print the environment variables of the process in the ps output format, text-processing (parsing/filtering) is required to see the environment variables as a list.

Solaris (not asked, but for reference I will post here):

/usr/ucb/ps -wwwe <pid>

or

pargs -e <pid> 

EDIT: /proc/pid/environ is not updated! I stand corrected. Verification process is below. However, the children from which the process are fork'd inherit the process environment variable and it is visible in their respective /proc/self/environ file. (Use strings)

With in the shell: here xargs is a child process and hence inherits the environment variable and also reflects in its /proc/self/environ file.

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

Checking it from other session, where the terminal/session is not the child process of the shell where the environment variable is set.

Verifying from another terminal/session on the same host:

terminal1: : Note that printenv is fork'd and is a child process of bash and hence it reads its own environ file.

[centos@centos t]$ echo $$
2610
[centos@centos t]$ export SPIDEY=NIKHIL
[centos@centos t]$ printenv | grep SPIDEY
SPIDEY=NIKHIL
[centos@centos t]$ 

terminal2: on the same host -- do not launch it with in the same shell where the above variable was set, launch the terminal separately.

[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 ~]$ 
Jeff Schaller
  • 67,283
  • 35
  • 116
  • 255
  • 2
    I do export foo=bar in the one bash's session (pid xxxx), then do cat /proc/xxxx/environ | tr \\0 \\n in other bash's session and I don't see foo. –  Jan 14 '12 at 18:11
  • I updated the above answer with an example checking the same process within shell. – Nikhil Mulley Jan 14 '12 at 18:52
  • You are correct. I stand corrected. Thanks. I now have to go and read my manuals to check the environment variables of a different process with in the user process group. – Nikhil Mulley Jan 14 '12 at 19:35
  • 1
    One more thing: I tried checking the environment attaching the gdb to the pid, but still no reference there. The environment variables block in the memory gets reallocated whenever there is a change and is not reflecting in its own process' environ file in the proc filesystem, but however allows to be inherited by the child process. That means this could get easier to know intrinsic details when the fork happens, how does the child process gets environment variables copied as is. – Nikhil Mulley Jan 14 '12 at 19:48
  • I hope @Gilles would throw some of his torch light on this.. :-) – Nikhil Mulley Jan 14 '12 at 20:22
  • /usr/ucb/ps doesn't seem to work on Solaris 11, but pargs -e did the trick, thanks! – tresf Apr 24 '20 at 22:21
29

/proc/$pid/environ does update if the process changes its own environment. But many programs don't bother changing their own environment, because it's a bit pointless: a program's environment is not visible through normal channels, only through /proc and ps, and even not every unix variant has this kind of feature, so applications don't rely on it.

As far as the kernel is concerned, the environment only appears as the argument of the execve system call that starts the program. Linux exposes an area in memory through /proc, and some programs update this area while others don't. In particular, I don't think any shell updates this area. As the area has a fixed size, it would be impossible to add new variables or change the length of a value.

  • so, effectively there is no way to access the process' *envp (array of pointers to environment settings). @Gilles, can you please show if it is possible to attach the debugger and read the array of pointers to environment settings. – Nikhil Mulley Jan 15 '12 at 07:29
  • 3
    @Nikhil Sure, it is. But just because you write PATH=foo in a shell doesn't mean the shell is going to modify *envp. In some shells, that only updated an internal data structure, and it's the external program execution code that updates *envp. Look at assign_in_env in variables.c in the bash source, for example. – Gilles 'SO- stop being evil' Jan 15 '12 at 17:00
  • 14
    @Gilles: This answer is at best misleading (-1). The environment in /proc/$$/environ is read from the process's stack. See fs/proc/base.c. This is the initial environment. It is never updated and in fact can't be. The environment that the libc setenv uses is allocated on the heap and initialized with the contents of the environment in the stack. If the process calls libc's fork then libc makes the sys_fork call using the heap allocated environment for the child process. – Jonathan Ben-Avraham Mar 29 '13 at 13:24
  • 8
    @JonathanBen-Avraham You're right that the initial environment isn't updated in any shell. However that area is not read only under Linux, I've encountered programs that use it to report their status (status reports through argv are more common but both exist). – Gilles 'SO- stop being evil' Mar 29 '13 at 21:23
23

Well the following is not related with author's real intentions, but if you really want to "READ" the /proc/<pid>/environ, you may try

strings /proc/<pid>/environ

which is better than cat it.

fibonacci
  • 1,035
  • 5
    +1 for strings. Keep it simple. – Ed Randall Jan 27 '18 at 20:34
  • Agree @EdRandall, this feels like the easier approach vs xargs --null. – Per Lundberg Oct 23 '18 at 07:45
  • 4
    The "file" is null terminated, replace nulls with new-line and normal tools work again (with the usual caveats), e.g.: tr '\0' '\n' < /proc/$$/environ | ... – Thor Mar 22 '19 at 17:52
  • As long as the environment variable itself does not contain newlines. Shells like BASH sometimes do that for 'exported functions'. – anthony Nov 14 '19 at 06:37
  • 4
    By default strings only prints strings of length >= 4. Environment variables like A=1 will be ignored. Also, whitespace (expect space and tab) is ignored. You can change this by using strings -n2 -w instead. – Socowi Mar 16 '21 at 23:48
  • tr '\0' '\n' < /proc/$pid/environ should be accepted answer – quant2016 Nov 01 '23 at 11:42
3

I opted to temporarily attach a gdb session to the program and call getenv from there. It probably does not work with all programs, and it's quite costly, but this often works:

echo 'p (char *) getenv("PWD")' | \
     gdb --quiet -p $(pidof emacs) | \
     sed -n 's/.*"\(.*\)"$/\1/p'
Michaël
  • 774
  • 1
    I was not aware of exactly how to use gdb to do that... Great process! Thanks! Fortunately (for my security concerns) this often will not work... On Ubuntu 20.04 basically default install running the same against vim, I got: Could not attach to process. If your uid matches the uid of the target process, check the setting of /proc/sys/kernel/yama/ptrace_scope, or try again as the root user. For more details, see /etc/sysctl.d/10-ptrace.conf ptrace: Operation not permitted. – erwin Nov 06 '20 at 00:00
  • 1
    Interesting, it seems the default for ptrace_scope actually changed very recently. I did have to go sudo sh -c 'echo 0 > /proc/sys/kernel/yama/ptrace_scope' . – Michaël Nov 13 '20 at 21:20
1

I implemented a project that does what previous answers suggested to retrieve updated variables from programs that don't update /proc/<pid>/env: https://github.com/LlinksRechts/getenv

This attaches to a process using ptrace, injects some code that calls getenv, prints the result, and resumes the program where it left off.

This of course follows the same limitations as the approach that uses gdb (namely requiring to set ptrace_scope), but is much faster than that (benchmarks on my PC showed ~200ms for gdb, but only 0.4ms for getenv).

Lukor
  • 131
  • I'm commenting here as your getenv GitHub project has issue reporting disabled: on my system (Debian 11 bullseye), ./getenv does not show updated environment variables of other processes. – Abdull Aug 09 '23 at 08:14
  • @Abdull I enabled issues - could you give a bit more details there? (which program you tried, maybe a minimal reproduction,...) – Lukor Aug 09 '23 at 19:21
1

I have created the envps utility for this:

$ envps 607
LaunchInstanceID=5345193B
XPC_SERVICE_NAME=no.example.ExampleExtension
PATH=/usr/bin:/bin:/usr/sbin:/sbin

You can build it yourself using make or install it using Homebrew:

$ brew install henrik242/brew/envps
neu242
  • 1,738