31

If I examine /proc/1/environ I can see a null-byte-delimited string of process 1's environment variables. I'd like to bring these variables into my current environment. Is there an easy way to do this?

The proc man page gives me a snippet which helps be print out each environment variable on a line-by-line basis (cat /proc/1/environ; echo) | tr '\000' '\n'. This helps me verify the contents are correct, but what I really need to do is source these variables into my current bash session.

How do I do that?

Braiam
  • 35,991

10 Answers10

31

The following will convert each environment variable into an export statement, properly quoted for reading into a shell (because LS_COLORS, for example, is likely to have semicolons in it), then sources it.

[The printf in /usr/bin, unfortunately, generally doesn't support %q, so we need to call the one built into bash.]

. <(xargs -0 bash -c 'printf "export %q\n" "$@"' -- < /proc/nnn/environ)
John Kugelman
  • 2,057
  • 2
  • 16
  • 23
Mark Plotnick
  • 25,413
  • 3
  • 64
  • 82
  • I suggest . <(xargs -0 bash -c 'printf "export %q\n" "$@"' -- < /proc/nnn/environ), which will handle variables with quotes in them properly as well. – John Kugelman Apr 17 '14 at 15:48
  • @JohnKugelman Thanks very much for the improvement, using "$@" instead of '{}'. For those wondering about the -- argument in his improved answer: positional arguments to bash -c command_string are assigned starting at $0, while "$@" expands to include arguments starting at $1. The argument -- gets assigned to $0. – Mark Plotnick Apr 17 '14 at 17:45
  • Caveat: /proc/"${SOME_PID}/environ shows the environment variables of the process with PID $SOME_PID at the moment that process was started. Changes to that process' environment variables (including additions, removals, and modifications) are not reflected in /proc/"${SOME_PID}/environ. – Abdull Aug 08 '23 at 10:03
12

In bash you can do the following. This will work for all possible contents of the variables and avoids eval:

while IFS= read -rd '' var; do declare +x "$var"; done </proc/$PID/environ

This will declare the read variables as shell variables in the running shell. To export the variables into the running shell environment instead:

while IFS= read -rd '' var; do export "$var"; done </proc/$PID/environ
Graeme
  • 34,027
  • Caveat: /proc/"${SOME_PID}/environ shows the environment variables of the process with PID $SOME_PID at the moment that process was started. Changes to that process' environment variables (including additions, removals, and modifications) are not reflected in /proc/"${SOME_PID}/environ. – Abdull Aug 14 '23 at 09:28
11

In this answer, I assume a system where /proc/$pid/environ returns the environment of the process with the specified PID, with null bytes between variable definitions. (So Linux, Cygwin or Solaris (?)).

Zsh

export "${(@ps:\000:)$(</proc/$pid/environ)}"

(Pretty simple as zsh goes: an input redirection with no command <FILE is equivalent to cat FILE. The output of the command substitution undergoes parameter expansion with the flags ps:\000: meaning “split on null bytes”, and @ meaning “if the whole thing is in double quotes then treat each array element as a separate field” (generalizing "$@").)

Bash, mksh

while IFS= read -r -d "" PWD; do export "$PWD"; done </proc/$pid/environ
PWD=$(pwd)

(In these shells, an empty delimiter passed to read results in null bytes being separators. I use PWD as a temporary variable name to avoid clobbering another variable that might end up being imported. While you could technically import PWD as well, it would only stay put until the next cd.)

POSIX

POSIX portability isn't that interesting for this question, because it only applies to systems that have /proc/PID/environ. So the question is what Solaris sed supports — or whether Solaris has /proc/PID/environ, it didn't use to but I'm way behind the curve on Solaris features so it might nowadays. On Linux, GNU utilities and BusyBox are both null-safe, but with caveates.

If we do insist on POSIX portability, none of the POSIX text utilities are required to handle null bytes, so this is difficult. Here's a solution that assumes that awk supports a null byte as the record delimiter (nawk and gawk do, as does BusyBox awk, but mawk doesn't).

eval $(</proc/$pid/environ awk -v RS='\0' '{gsub("\047", "\047\\\047\047"); print "export \047" $0 "\047"}')

BusyBox awk (which is the version commonly found on embedded Linux systems) does support null bytes but not setting RS to "\0" in a BEGIN block and not the command line syntax above; however it does support -v 'RS="\0"'. I haven't investigated why, this looks like a bug in my version (Debian wheezy).

(Wrap all lines null-separated records in single quotes "\047", after escaping the single quotes inside values.)

Caveats

Beware that any of these might attempt to set read-only variables (if your shell has read-only variables).

  • I did finally come back to this. I figured out a foolproof means of doing this or any null handling in all the shells i know about pretty simply. See my new answer. – mikeserv Apr 19 '14 at 14:14
  • Caveat: /proc/"${SOME_PID}/environ shows the environment variables of the process with PID $SOME_PID at the moment that process was started. Changes to that process' environment variables (including additions, removals, and modifications) are not reflected in /proc/"${SOME_PID}/environ. – Abdull Aug 14 '23 at 09:28
  • @Abdull Not quite. /proc/$pid/environ does show the process's current environment. However, the environment is in a fixed area of the process which can't be conveniently modified (in particular because the area is fixed-size). So any program that offers a way to modify its environment variables, including shells, is modifying a copy of its environment which can't be seen from the outside (except by using a debugger). – Gilles 'SO- stop being evil' Aug 15 '23 at 17:36
  • @Gilles'SO-stopbeingevil', quoting man 5 proc: "/proc/pid/environ: This file contains the initial environment* that was set when the currently executing program was started via execve(2). [...] If, after an execve(2), the process modifies its environment (e.g., by calling functions such as putenv(3) or modifying the environ(7) variable directly), this file will not reflect those changes.*" – Abdull Aug 17 '23 at 16:26
  • @Abdull putenv modifies a copy of the environment. That's why it's not reflected in /proc/$pid/environ. – Gilles 'SO- stop being evil' Aug 17 '23 at 16:31
6

I went round and round with this. I was frustrated with the portability of null bytes. It didn't sit well with me that there was no reliable way to handle them in a shell. So I kept looking. The truth is I found several ways to do this, only a couple of which are noted in my other answer. But the results were at least two shell functions that work like this:

_pidenv ${psrc=$$} ; _zedlmt <$near_any_type_of_file

First I'll talk about the \0 delimiting. It actually is pretty easy to do. Here's the function:

_zedlmt() { od -t x1 -w1 -v  | sed -n '
    /.* \(..\)$/s//\1/
    /00/!{H;b};s///
    x;s/\n/\\x/gp;x;h'
}

Basically od takes stdin and writes to its stdout each byte it receives in hexadecimal one per line.

printf 'This\0is\0a\0lot\0\of\0\nulls.' |
    od -t x1 -w1 -v
    #output
0000000 54
0000001 68
0000002 69
0000003 73
0000004 00
0000005 69
0000006 73
    #and so on

I bet you can guess which is the \0null, right? Written out like that it's easy to handle with any sed. sed just saves the last two chars in each line until it encounters a null at which point it replaces the intermediate newlines with printf friendly format code and prints the string. The result is a \0null delimited array of hex byte strings. Look:

printf %b\\n $(printf 'Fewer\0nulls\0here\0.' |
    _zedlmt | tee /dev/stderr)
    #output
\x46\x65\x77\x65\x72
\x6e\x75\x6c\x6c\x73
\x68\x65\x72\x65
\x2e
Fewer
nulls
here
.

I piped the above to tee so you could see both the output of the command susbstitution and the result of printf's processing. I hope you'll notice that the subshell actually isn't quoted either but printf still split only at the \0null delimiter. Look:

printf %b\\n $(printf \
        "Fe\n\"w\"er\0'nu\t'll\\'s\0h    ere\0." |
_zedlmt | tee /dev/stderr)
    #output
\x46\x65\x0a\x22\x77\x22\x65\x72
\x27\x6e\x75\x09\x27\x6c\x6c\x27\x73
\x68\x20\x20\x20\x20\x65\x72\x65
\x2e
Fe
"w"er
'nu     'll's
h    ere
.

No quotes on that expansion either - it doesn't matter if you quote it or not. This is because the bite values come through unseparated except for the one \newline generated for each time sed prints a string. Word-splitting doesn't apply. And that's what makes this possible:

_pidenv() { ps -p $1 >/dev/null 2>&1 &&
        [ -z "${1#"$psrc"}" ] && . /dev/fd/3 ||
        cat <&3 ; unset psrc pcat
} 3<<STATE
        $( [ -z "${1#${pcat=$psrc}}" ] &&
        pcat='$(printf %%b "%s")' || pcat="%b"
        xeq="$(printf '\\x%x' "'=")"
        for x in $( _zedlmt </proc/$1/environ ) ; do
        printf "%b=$pcat\n" "${x%%"$xeq"*}" "${x#*"$xeq"}"
        done)
#END
STATE

The above function uses _zedlmt to either ${pcat} a prepared stream of byte code for environment sourcing of any process that can be found in /proc, or to directly .dot ${psrc} the same in the current shell, or without a parameter, to display a processed output of same to the terminal like set or printenv will. All you need is a $pid - any readable /proc/$pid/environ file will do.

You use it like this:

#output like printenv for any running process
_pidenv $pid 

#save human friendly env file
_pidenv $pid >/preparsed/env/file 

#save unparsed file for sourcing at any time
_pidenv ${pcat=$pid} >/sourcable/env.save 

#.dot source any pid's $env from any file stream    
_pidenv ${pcat=$pid} | sh -c '. /dev/stdin'

#feed any pid's env in on a heredoc filedescriptor
su -c '. /dev/fd/4' 4<<ENV
    $( _pidenv ${pcat=$pid} )
ENV

#.dot sources any $pid's $env in the current shell
_pidenv ${psrc=$pid} 

But what's the difference between human friendly and sourcable? Well, the difference there is what makes this answer different than every other here - including my other one. Every other answer depends on shell quoting in some way or another to handle all edge-cases. It simply doesn't work that well. Please believe me - I've TRIED. Look:

_pidenv ${pcat=$$}
    #output
LC_COLLATE=$(printf %b "\x43")
GREP_COLOR=$(printf %b "\x33\x37\x3b\x34\x35")
GREP_OPTIONS=$(printf %b "\x2d\x2d\x63\x6f\x6c\x6f\x72\x3d\x61\x75\x74\x6f")
LESS_TERMCAP_mb=$(printf %b "\x1b\x5b\x30\x31\x3b\x33\x31\x6d")
LESS_TERMCAP_md=$(printf %b "\x1b\x5b\x30\x31\x3b\x33\x31\x6d")
LESS_TERMCAP_me=$(printf %b "\x1b\x5b\x30\x6d")
LESS_TERMCAP_se=$(printf %b "\x1b\x5b\x30\x6d")
LESS_TERMCAP_so=$(printf %b "\x1b\x5b\x30\x30\x3b\x34\x37\x3b\x33\x30\x6d")
LESS_TERMCAP_ue=$(printf %b "\x1b\x5b\x30\x6d")

NO amount of funky characters or contained quoting can break this because the bytes for each value are not evaluated until the very instant the content is sourced. And we already know it worked as a value at least once - there is no parsing or quote protection necessary here because this is a byte-for-byte copy of the original value.

The function first evaluates the $var names and waits for checks to complete before .dot sourcing the here-doc fed it on file-descriptor 3. Before it sources it that's what it looks like. It's fool-proof. And POSIX portable. Well, at least the \0null handling is POSIX portable - the /process filesystem is obviously Linux specific. And that's why there are two functions.

mikeserv
  • 58,310
3

Using source and process substitution:

source <(sed -r -e 's/([^\x00]*)\x00/export \1\n/g' /proc/1/environ)

Shortly:

. <(sed -r -e 's/([^\x00]*)\x00/export \1\n/g' /proc/1/environ)

Using eval and command substitution:

eval `sed -r -e 's/([^\x00]*)\x00/export \1\n/g' /proc/1/environ`

The sed call can be replaced with an awk call:

awk -vRS='\x00' '{ print "export", $0 }' /proc/1/environ

But don't forget that it doesn't clear any environment variables that are not in pid 1.

3

It's worth noting that processes can have environment variables which are not valid Bash/Sh/*sh variables -- POSIX recommends but does not require that environment variables have names matching ^[a-zA-Z0-9_][a-zA-Z0-9_]*$.

To generate a listing of variables that are shell compatible from another process's environment, in Bash:

function env_from_proc {
  local pid="$1" skipped=( )
  cat /proc/"$pid"/environ | while read -r -d "" record
  do
    if [[ $record =~ ^[a-zA-Z_][a-zA-Z0-9_]*= ]]
    then printf "export %q\n" "$record"
    else skipped+=( "$record" )
    fi
  done
  echo "Skipped non-shell-compatible vars: ${skipped[@]%%=*}" >&2
}

Similarly, to load them:

function env_from_proc {
  local pid="$1" skipped=( )
  while read -r -d "" record
  do
    if [[ $record =~ ^[a-zA-Z_][a-zA-Z0-9_]*= ]]
    then export "$(printf %q "$record")"
    else skipped+=( "$record" )
    fi
  done < /proc/"$pid"/environ
  echo "Skipped non-shell-compatible vars: ${skipped[@]%%=*}" >&2
}

This problem comes up only occasionally but when it does...

  • Caveat: /proc/"${SOME_PID}/environ shows the environment variables of the process with PID $SOME_PID at the moment that process was started. Changes to that process' environment variables (including additions, removals, and modifications) are not reflected in /proc/"${SOME_PID}/environ. – Abdull Aug 14 '23 at 09:29
1

bash:

export $(cat /proc/${pid}/environ|xargs -0 echo)
ruief
  • 336
  • Caveat: /proc/"${SOME_PID}/environ shows the environment variables of the process with PID $SOME_PID at the moment that process was started. Changes to that process' environment variables (including additions, removals, and modifications) are not reflected in /proc/"${SOME_PID}/environ. – Abdull Aug 14 '23 at 09:29
0

I think this is POSIX portable:

. <<ENV /dev/stdin
    $(sed -n 'H;${x;s/\(^\|\x00\)\([^=]*.\)\([^\x00]*\)/\2\x27\3\x27\n/gp}' \
       /proc/$pid/environ)
ENV

But @Gilles makes a good point - sed will probably handle nulls, but maybe not. So there's this (I Really think so this time) actually POSIX portable method, too:

s=$$SED$$
sed 's/'\''/'$s'/;1s/^./'\''&/' </proc/"$$"/environ |
tr '\0' "'" |
sed 's/'\''/&\n&/g' |
sed '1d;$d;s/^\('\''\)\([^=]*.\)/\2\1/;s/'$s'/'\\\''/g'

Still, if you've got GNU sed you need only do:

sed -z 's/^[^=]*./&'\''/;s/$/'\''\n/' </proc/"$$"/environ

  #BOTH METHODS OUTPUT:

enter image description here

Well, POSIX portable that is except for the /dev/... which is not specified but you can pretty much expect that syntax to behave same on most Unices.

Now if this has anything to do with your other question, you might like to use it like this:

nsenter -m -u -i -n -p -t $PID /bin/bash 5<<ENV --rcfile=/dev/fd/5 
    $(sed -z 's/^[^=]*./&'\''/;s/$/'\''\n/' </proc/"$$"/environ)
ENV

The here-doc is extremely helpful in that it keeps the shell from screwing with any of the quoting we work so hard to handle in the subshell and also provides us a reliable path to a .dot sourceable file rather than, again, a subshell or a shell variable. Others here use the <(process substitution) bashism which works in much the same way - only it's definitely an anonymous |pipe whereas POSIX only specifies an iohere for here-docs and so it can be any type of file, though, in practice, it's usually a temp file. (dash, on the other hand, does use anonymous |pipes for here-docs). The unfortunate thing about process substitution though, is it's also shell dependent - which might be an especially annoying issue if you're working with init.

This also works with |pipes of course, but then you lose the environment again in the end when the |pipe's state evaporates with its subshell. Then again, this works:

sed '...;a\exec <>/dev/tty' /proc/$pid/environ | sh -i 

The sed statement itself works by holding every line in memory until it reaches the last, at which time it performs a global replace handling quoting and inserting newlines where appropriate by anchoring on the nulls. Fairly simple really.

In the dash picture you'll see I opted to eschew the \ mess and added the GNU specific -r option to sed. But that's just cause it was less to type. It works either way, as you can see in the zsh image.

Here's zsh:

enter image description here

And here's dash doing the vary same thing:

enter image description here

Even terminal escapes come through unscathed:

enter image description here

mikeserv
  • 58,310
  • This is not POSIX-portable because sed is not required to handle null bytes. (That being said, POSIX portability isn't that interesting for this question, because it only applies to systems that have /proc/PID/environ. So the question is what Solaris sed supports — or whether Solaris has /proc/PID/environ, it didn't use to but I'm way behind the curve on Solaris features so it might nowadays.) – Gilles 'SO- stop being evil' Apr 17 '14 at 07:21
  • @Gilles No. But sed is required to handle ascii hexadecimal, of which the null byte is one. Besides, i actually just thought if a much easier way to do this still. – mikeserv Apr 17 '14 at 07:28
  • No, POSIX says that “The input files shall be text files” (for sed and other text utilities) and defines text files as “file that contains characters organized into one or more lines. The lines do not contain NUL characters (…)”. And by the way the \xNN syntax is not required in POSIX, not even the \OOO octal syntax (in C strings and in awk, yes, but not in sed regexps). – Gilles 'SO- stop being evil' Apr 17 '14 at 07:33
  • @Gilles you've got a point. I looked all over and I couldn't find what I thought I could before. So I did it differently. Editing now. – mikeserv Apr 17 '14 at 11:29
  • As far as I can tell, Solaris doesn't have /proc/PID/environ after all (it has several other Linux-like entries in /proc/PID, but not environ). So a portable solution doesn't need to go beyond Linux tools after all, meaning GNU sed or BusyBox sed. Both do support \x00 in a regexp, so your code is as portable as needed (but not POSIX). It's overly complex though. – Gilles 'SO- stop being evil' Apr 17 '14 at 11:45
  • @Gilles - I was still editing. Which i do say in there, by the way. – mikeserv Apr 17 '14 at 11:48
  • Your first snippet (already present before the edit), using \x00, is fine for Linux, even if it isn't POSIX. – Gilles 'SO- stop being evil' Apr 17 '14 at 11:51
  • @Gilles Yeah, but sometimes thats not always the point to me. I do these things because i like to learn. I learned a great deal about sed doing this. – mikeserv Apr 17 '14 at 11:54
0

Here is solution based on Gilles answer with few tweaks if you want to define ONLY missing variables that are not defined in your scope but are defined for process $PID

while IFS= read -rd '' var; do if [ ! -v "${var%%=*}" ]; then export "$var"; fi; done </proc/$PID/environ
Šime
  • 101
-2
eval \`(cat /proc/1/environ; echo) | tr '\000' '\n'\`
slm
  • 369,824