22

In a posix-compatible way that works with multiple implementations, how can I print the list of currently defined environment variable without their values?

On some implementations (mksh, freebsd /bin/sh), just using export by itself will fit the bill:

$ export
FOO2
FOO

But for some other implementations (bash, zsh, dash), export also shows the value. With bash, for example:

$ export
export FOO2='as  df\
  asdk=fja:\
asd=fa\
asdf'
export FOO='sjfkasjfd  kjasdf:\
   asdkj=fkajdsf:\
       :askjfkajsf=asdfkj:\
   safdkj'
$ printenv | sed -n l
FOO2=as\tdf\$
  asdk=fja:\$
asd=fa\$
asdf$
FOO=sjfkasjfd  kjasdf:\$
   asdkj=fkajdsf:\$
\t:askjfkajsf=asdfkj:\$
   safdkj$

Other options like env or printenv don't have options to print just the variable names without values, at least not on the linux and freebsd platforms I have tried.

Piping to awk/sed/etc. or trimming the list with parameter expansion techniques (e.g., ${foo%%=*}) is acceptable, but it has to work with values that may span lines and have = and whitespace in the value (see example above).

Answers specific to particular shell implementations are interesting, but I am primarily looking for something that is compatible across implementations.

8
  • 2
    If you're going to parse something, go with the output of export -p which is specified by POSIX to generate output that is also suitable for input in the shell.
    – Kusalananda
    Commented Apr 21, 2018 at 16:01
  • I don't need anything to input to a shell. I just want the names of the environment variables. So the extra cruft (e.g., the 'export' word at the front of each line) would just have to be removed anyway. Why would I want to use export -p for this?
    – Juan
    Commented Apr 21, 2018 at 17:22
  • 1
    You'd want to use export -p because that would give you a consistent output across all POSIX shells, which you said you wanted.
    – Kusalananda
    Commented Apr 21, 2018 at 17:25
  • I want a solution that will print just the variable names that is a consistent solution across shells. export -p does not fit the first requirement - printing just variable names without values.
    – Juan
    Commented Apr 21, 2018 at 17:30
  • 1
    @Kapocsi Yep, I meant set -o posix
    – Juan
    Commented Aug 19, 2021 at 21:55

6 Answers 6

16

It's pretty easy in awk.

awk 'BEGIN{for(v in ENVIRON) print v}'

However beware some awk implementations add environment variables of their own (e.g. GNU awk adds AWKPATH and AWKLIBPATH to ENVIRON).

The output is ambiguous if the name of an environment variable contains a newline, which is extremely unusual but technically possible. A pure sh solution would be difficult. Your best bet is to start with export -p but massaging it in pure sh is difficult. You can use sed to massage the output of export -p, then use eval to get the shell to remove what it quoted. Bash and zsh print non-standard prefixes.

report () { echo "${1%%=*}"; };
eval "$(export -p | sed "s/^export /report /;
                         s/^declare -x /report /;
                         s/typeset -x /report /")"

Note that depending on the shell, export -p may or may not show variables whose name is not valid in the shell, and if it doesn't, then it may or may not quote the names properly. For example, dash, mksh and zsh omit variables whose name includes a newline, BusyBox dash and ksh93 print them raw, and bash prints them raw without their value. If you need to defend against untrusted input, don't rely on a pure POSIX solution, and definitely don't call eval on anything derived from the output of export -p.

4
  • I'm not sure how "easy" it really is with sed(1) with multiline values - I'd like to see that. But thanks for the awk(1) method. I think this is the most portable way so far (although I don't think the exit is necessary).
    – Juan
    Commented Apr 23, 2018 at 12:34
  • 1
    Note that if you get FOO<newline>BAR, you don't know whether it's a FOO<newline>BAR environment variable (which export -p wouldn't show with most shells, see env $'FOO\nBAR=test' awk 'BEGIN{for (v in ENVIRON) print v}') or both a FOO and BAR environment variable. Commented Apr 23, 2018 at 12:50
  • Note that GNU awks sets environment variables of its own (AWKPATH and AWKLIBPATH on my system) Commented Apr 23, 2018 at 12:54
  • 1
    @StéphaneChazelas - re: embedded newline in var name - agreed - hard to defend against that with shell code. Re: AWKPATH & AWKLIBPATH pollution, I noticed that insidious insertion, too. That's gnu awk, not bsd awk.
    – Juan
    Commented Apr 24, 2018 at 0:18
5

Here is my solution, which nicely handles environment variables having multi-line values:

env -0 | cut -z -f1 -d= | tr '\0' '\n' | sort | column

This will print the environment using NUL to delimit variables instead of newlines (env -0).

Then we eliminate the variable values using cut -z -f1 -d=, where the -z option specifies NUL-delimited records, -f1 is the first field, and -d= says that fields are delimited by =.

tr '\0' '\n' will convert the NUL delimiters to newlines.

Then I like to sort the variable names and display them in columns.

3
  • 1
    Note that env -0 is not POSIX. Neither is cut -z or the column utility. The tr and sort commands used in your answer are however compliant.
    – Kusalananda
    Commented Feb 5, 2021 at 17:31
  • @Kusalananda, but here sort should go before tr and with -z. GNU sort has had -z longer before GNU cut and GNU env, that would be the least unportable of the lot. Commented Feb 5, 2021 at 17:35
  • True… I had overlooked the requirement for POSIX compliance. Commented Feb 5, 2021 at 21:04
3

I like simple things; this will work for POSIX systems:

printenv | sed 's;=.*;;' | sort
HOME
HOSTNAME
PATH
PWD
SHLVL
TERM
8
  • 9
    export AAA=$'multi\nBBB=line' Commented Sep 26, 2019 at 17:35
  • 3
    @todd_dsm - This fails for environment variables that have values that span more than one line (e.g., sometimes TERMCAP - I see this when in a screen(1) session, IIRC). So this answer does not meet the requirements described in the original question. I also like simple, but this does not work generally.
    – Juan
    Commented Nov 3, 2019 at 17:46
  • The tidbit you edited out in your original post was interesting for bash: compgen -e. It doesn't help for my portable scripting (e.g., when bash is not available), but it's interesting.
    – Juan
    Commented Nov 3, 2019 at 19:21
  • just curious, why restrict yourself to the bourne (posix) shell? being posix-compliant is great but you lose a great deal of functionality in that pursuit. you can be portable and NOT posix-compliant with bash; it's part of the GNU family.
    – todd_dsm
    Commented Nov 5, 2019 at 0:24
  • Using dash in debian, I get the same results with the command above or, the modified printenv | sed 's;*=.;;' | sort to get the values. I exported the variable yo and assigned it your first comment above; it printed as expected, with multiple lines. not sure what you're experiencing but there shouldn't be truncated output. run the command in the shell; whatever it outputs there is how it should work; expect no truncating. Then, within the context of TERMCAP/screen/iirc; should be the same. If the output does not match then it's likely an issue with one of those programs.
    – todd_dsm
    Commented Nov 5, 2019 at 0:45
0

This was enough for me to get a list of all exported variable names that handled quoted and multi-lined values:

export | egrep ^declare | cut -d= -f1 | cut -d' ' -f3

As Stephane pointed out, this works in bash, and you'd be better using compgen -e which is bash-only. If you want a 1-liner that works the same in bash, dash and zsh, you can use this instead:

export | egrep '^(declare|export)' | sed -E 's/(^declare -x |export )//' | cut -d= -f1 | cut -d' ' -f2

... although this won't work for multi-line values in dash.

2
  • I'd only expect that to work with the bash shell and only if it's not in POSIX mode and variable names and values don't contain newline characters and name doesn't contain spaces. The OP specifically asked for a POSIX shell solution. If you're going to use a bash-specific solution, you might as well use compgen -e Commented Aug 17, 2023 at 19:24
  • This works for the case where variable values have newlines & spaces, that's the case that I wrote this for... I didn't realize you could have newlines in a variable name. It is an example for bash, because the OP provided an example for bash that didn't work, and this snippet does.
    – nevelis
    Commented Sep 7, 2023 at 1:50
-1

Another approach, with grep:

printenv|grep -o "^[^=]\+"
4
  • 1
    This breaks with variables whose values contain =. Also note that -o is not standard with grep.
    – Kusalananda
    Commented Dec 31, 2021 at 12:37
  • @they thanks for the comment. Good catch, I've improved the answer to make it cut on the first = in a line (for the record, before edit the answer was printenv|grep -o "^.*="|tr -d '='). Also thanks for spotting non-standard -o - I must admit it just works for me, but I have no idea how common it is.
    – TMG
    Commented Dec 31, 2021 at 12:49
  • I just mentioned the use of -o since the question asks for "a posix-compatible way [...]". Your updated answer solves the issue with = mentioned in my previous comment, but also introduces \+, which only works with GNU grep AFAIK.
    – Kusalananda
    Commented Dec 31, 2021 at 13:15
  • This also fails in the case of an environment variable value that spans multiple lines.
    – Juan
    Commented Sep 16, 2022 at 20:11
-1

If you only want to print from a subset of variable names, matching a distinct pattern:

env | awk -F= '$1 ~ /^PREFIX_.*_SUFFIX$/ && /=/ { print $1 }'

Example pattern matches variables named like PREFIX_ANYTHING_SUFFIX.

Obviously test that the chosen pattern is sufficiently distinct and avoids conflict with values of any multi-line variables (such as TERM).

1
  • This can fail for multiline entries including that might hit a false positive match in the value of the env var for whatever pattern one may want to match, rare as such cases might be. Like the simplicity, though. Also doesn't answer the more general (print all env var names) original question, of course (where the pattern in this awk snippet would be just '.'). It also can match lines that have no = sign at all (e.g., echo foo | awk -F= '$1 ~ /.+/ {print $1}')
    – Juan
    Commented Mar 2 at 17:37

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .