# How do I source another process's environment variables?

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?

-

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)  - 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 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 at 17:45 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 at 14:14

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

-

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. - 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.

-
Is the export redundant in @fr00tyl00p's answer? Cause if not it seems pretty important –  thedeeno Apr 16 at 20:52
Yes, the export is needed. I'll fix it up. –  Pavel Šimerda Apr 16 at 20:59
All of these commands choke on values that contain newlines and (depending on the command) other characters. –  Gilles Apr 16 at 22:54
Correct. Will keep the answer for reference anyway. –  Pavel Šimerda Apr 18 at 8:26

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:


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:

And here's dash doing the vary same thing:

Even terminal escapes come through unscathed:

-
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 Apr 17 at 7: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 at 7: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 Apr 17 at 7: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 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 Apr 17 at 11:45
eval \(cat /proc/1/environ; echo) | tr '\000' '\n'\

-
This doesn't work if any value contains any shell special character. –  Gilles Apr 16 at 23:00