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 \n
ewline 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.
. <(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"$@"
instead of'{}'
. For those wondering about the--
argument in his improved answer: positional arguments tobash -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/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