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. 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. Apr 17 '14 at 17:45

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

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 (?)).


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

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


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

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/

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

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)
'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"}"

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} )

#.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=$$}
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)


. <(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 Apr 16 '14 at 20:52
  • Yes, the export is needed. I'll fix it up. Apr 16 '14 at 20:59
  • 3
    All of these commands choke on values that contain newlines and (depending on the command) other characters. Apr 16 '14 at 22:54
  • Correct. Will keep the answer for reference anyway. Apr 18 '14 at 8:26

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
    if [[ $record =~ ^[a-zA-Z_][a-zA-Z0-9_]*= ]]
    then printf "export %q\n" "$record"
    else skipped+=( "$record" )
  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
    if [[ $record =~ ^[a-zA-Z_][a-zA-Z0-9_]*= ]]
    then export "$(printf %q "$record")"
    else skipped+=( "$record" )
  done < /proc/"$pid"/environ
  echo "Skipped non-shell-compatible vars: ${skipped[@]%%=*}" >&2

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


I think this is POSIX portable:

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

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:

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


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)

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

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

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.