18

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
    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
    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
    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
    Apr 21, 2018 at 17:30
  • 1
    @Kapocsi Yep, I meant set -o posix
    – Juan
    Aug 19, 2021 at 21:55

4 Answers 4

14

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
    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. Apr 23, 2018 at 12:50
  • Note that GNU awks sets environment variables of its own (AWKPATH and AWKLIBPATH on my system) Apr 23, 2018 at 12:54
  • @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
    Apr 24, 2018 at 0:18
2

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

printenv | sed 's;=.*;;' | sort
HOME
HOSTNAME
PATH
PWD
SHLVL
TERM
8
  • 8
    export AAA=$'multi\nBBB=line'
    – roaima
    Sep 26, 2019 at 17:35
  • 2
    @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
    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
    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
    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
    Nov 5, 2019 at 0:45
2

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
    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. Feb 5, 2021 at 17:35
  • True… I had overlooked the requirement for POSIX compliance. Feb 5, 2021 at 21:04
-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
    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
    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
    Dec 31, 2021 at 13:15
  • This also fails in the case of an environment variable value that spans multiple lines.
    – Juan
    Sep 16, 2022 at 20:11

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.