3

Environment variables with multiline values may confuse env's output:

# export A="B
> C=D
> E=F"
# env
A=B
C=D
E=F
TERM=xterm
SHELL=/bin/bash
USER=root
MAIL=/var/mail/root
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
PWD=/root
LANG=en_US.UTF-8
PS1=\h:\w\$
SHLVL=1
HOME=/root
LOGNAME=root
_=/usr/bin/env

In this case I can not just use awk -F= to extract all names because it shows wrong names C and E:

# env | awk -F= '{print $1}'
A
C
E
TERM
SHELL
USER
MAIL
PATH
PWD
LANG
PS1
SHLVL
HOME
LOGNAME
_

Then I figured out that env supports flag -0 to end each output line with 0 byte rather than newline, so using sed I could cut off the values in bash:

# env -0 | sed -e ':a;N;$!ba;s/\([^=]\+\)=\([^\x00]*\)\x00/\1\n/g'
A
TERM
SHELL
USER
MAIL
PATH
PWD
LANG
PS1
SHLVL
HOME
LOGNAME
_

But BusyBox's version of env does not support flag -0. Is there another way to do it?

spatar
  • 550
  • 1
  • 5
  • 15
  • Are you open to using another scripting language to do the job, like `python -c 'import os; print "".join(x + "\n" for x in os.environ),'` or `perl -le 'for (keys %ENV) { print $_ }'`, or do you need to confine yourself to a minimal set of shell tools? – Celada Jun 22 '12 at 18:15
  • @Celada: With Python it would be too easy :) Here I can use BusyBox only. – spatar Jun 22 '12 at 18:18

3 Answers3

4

If you are using linux (I thought busybox ran only on linux, but I may be wrong), /proc/self/environ contains a NUL separated environment in the same form as env -0 gives you. You can replace env -0 | with < /proc/self/environ.

sed -e ':a;N;$!ba;s/\([^=]\+\)=\([^\x00]*\)\x00/\1\n/g' < /proc/self/environ
camh
  • 40,988
  • 13
  • 62
  • 70
  • 1
    It's a good idea to parse `/proc/self/environ` but the script for `sed` that you used from my example does not work in busybox's implementation of sed the same way as in regular GNU sed. The first part of sed script `sed -e ':a;N;$!ba'` should accumulate all the lines into one ([seen here](http://stackoverflow.com/questions/1251999/sed-how-can-i-replace-a-newline-n#1252191)), but in busybox it also replaces all `\0` characters with `\n`. Try this command in bash and busybox: `echo -ne "A\0B" | sed -e ':a;N;$!ba' | hexdump -C`. – spatar Jun 23 '12 at 05:30
2

This is maybe not an elegant but working solution. It first extracts all possible names from env's output, then verifies each of them using shell's expansion ${parameter:+word}. And finally it removes duplicates, since the same variable name could be printed on several lines in env's output (as a real variable name and as a part of some other variable's multiline value):

env | awk -F= '/[a-zA-Z_][a-zA-Z_0-9]*=/ {
        if (!system("[ -n \"${" $1 "+y}\" ]")) print $1 }' | sort | uniq

PS: The | sort | uniq part can be also implemented in awk.

spatar
  • 550
  • 1
  • 5
  • 15
1

This will break if your environment variable values contain nulls. But that would also break from POSIX compatibility.

So it should work.

...unless you expect to encounter environment variable names which contain newlines. In that case the newlines will be truncated when they're displayed. However I can't seem to fathom how to create an environment variable with a newline in it in a busybox shell. My local shells balk at it at any rate. So I don't think that would be a big problem. As far as POSIX says, Other characters may be permitted by an implementation; applications shall tolerate the presence of such names. so I think stripping them and not erroring-out is tolerable.

# Read our environment; it's delimited by null bytes.
# Remove newlines
# Replace null bytes with newlines
# On each line, grab everything before the first '='
cat /proc/self/environ | tr -d '\n' | tr '\0' '\n' | cut -d '=' -f 1
inetknght
  • 4,300
  • 1
  • 26
  • 52