68

The bash builtin command set, if invoked without arguments, will print all shell and environment variables, but also all defined functions. This makes the output unusable for humans and difficult to grep.

How can I make the bash builtin command set print only the variables and not the functions?

Are there other commands which prints only the shell variables, without the functions?

Note: bash differentiates between shell variables and environment variables. see here Difference between environment variables and exported environment variables in bash

9 Answers 9

62

"Are there other commands which prints only the shell variables, without the functions?"

In man bash, in section SHELL BUILTIN COMMANDS (in the set section) it says: "In posix mode, only shell variables are listed."

(set -o posix; set)

note: () syntax spawns a subshell, if you don't like forking just use the more verbose version

set -o posix; set; set +o posix
7
  • 2
    By far the best solution
    – Ruslan
    Commented May 25, 2014 at 9:27
  • 2
    There are a bunch of usually uninteresting variables starting with _, which are easy to get rid of: (set -o posix ; set | grep -v ^_)
    – hyde
    Commented Nov 24, 2014 at 15:34
  • 3
    Just a note: the brackets are important in (set -o posix; set). Without the brackets the behavior of the current Bash shell would be changed to match the Posix standard. (Dunno what that means but looks important.) With the brackets, the Bash remains unchanged.
    – John Red
    Commented Sep 29, 2016 at 9:37
  • 2
    Another way to trigger the POSIX behavior : (POSIXLY_CORRECT= set) Commented Apr 7, 2018 at 13:28
  • 5
    Side effects: Sets POSIXLY_CORRECT=y and adds posix to $SHELLOPTS
    – Tom Hale
    Commented Apr 16, 2018 at 8:18
36

Here are some workarounds:

$ comm -3 <(declare | sort) <(declare -f | sort)

breakdown:

  1. declare prints every defined variable (exported or not) and function.
  2. declare -f prints only functions.
  3. comm -3 will remove all lines common to both. In effect this will remove the functions, leaving only the variables.

To only print variables which are not exported:

$ comm -3 <(comm -3 <(declare | sort) <(declare -f | sort)) <(env | sort)

Another workaround:

$ declare -p

This will only print the variables, but with some ugly attributes.

declare -- BASH="/bin/bash"
declare -ir BASHPID=""
declare -A BASH_ALIASES='()'
declare -a BASH_ARGC='()'
...

You can cut the attributes away using... cut:

$ declare -p | cut -d " " -f 3

One downside is that the value of IFS is interpreted instead of displayed.

compare:

$ comm -3 <(declare | sort) <(declare -f | sort)
...
IFS=$' \t\n'
...
$ declare -p | cut -d " " -f 3
...
IFS="
"
...

This makes it quite hard to use that output for further processing, because of that lone " in one line. Perhaps some IFS-fu can be done to prevent this.


Yet another workaround, using compgen:

$ compgen -v

The bash builtin compgen was meant to be used in completion scripts. To this end, compgen -v lists all defined variables. The downside: it lists only the variable names, not the values.

Here is a hack to also list the values.

$ compgen -v | while read var; do printf "%s=%q\n" "$var" "${!var}"; done

The advantage: it is a pure bash solution. The disadvantage: some values are messed up because of the interpretation through printf. Also the subshell from the pipe and/or the loop add some extra variables.

2
  • does your declare ...| cut pipe break if there are no attributes for a variable? I'm guessing that -- is used in that case, but I'm still uneasy. sed might be safer, i.e. declare -p | sed 's/^.* \([^ ]\+\)$/\1/'
    – jmtd
    Commented May 12, 2011 at 14:29
  • For declare -p | cut -d " " -f 3, you have to be careful, cuz some variables has multiline values.
    – Bruce
    Commented Nov 3, 2021 at 20:17
14

The typeset builtin strangely has an option to show functions only (-f) but not to show parameters only. Fortunately, typeset (or set) displays all parameters before all functions, function and parameter names cannot contain newlines or equal signs, and newlines in parameter values are quoted (they appear as \n). So you can stop at the first line that doesn't contain an equal sign:

set | awk -F '=' '! /^[0-9A-Z_a-z]+=/ {exit} {print $1}'

This only prints the parameter names; if you want the values, change print $1 to print $0.

Note that this prints all parameter names (parameters and variables are used synonymously here), not just environment variables (“environment variable” is synonymous with “exported parameters”).

Note also that this assumes there is no environment variable with a name that doesn't match bash's constraints on parameter names. Such variables cannot be created inside bash but can have been inherited from the environment when bash starts:

env 'foo ()
{
  =oops' bash
2
  • Great solution! I didn't even think of just stopping at the first line that doesn't have a '='. +1
    – Steven D
    Commented Oct 27, 2010 at 3:34
  • 4
    Equivalently, with sed: set | sed '/=/!Q' Commented Jul 5, 2018 at 17:18
8

I am unsure how one can make set print only variables. However by looking at the output of set, I was able to come up with the following that seems to grab just variables:

$ set | grep "^\([[:alnum:]]\|[[:punct:]]\)\+=" 

Basically, I am looking for lines that start with letters, numbers, or punctuation followed by a "=". From the output that I saw, this grabs all of the variables; however, I doubt that this is very portable.

If, as your title suggests, you want to subtract from this list the variables that are exported and thus get a list of non-exported variables, then you could do something like this

$ set | grep "^\([[:alnum:]]\|[[:punct:]]\)\+=" | sort > ./setvars && env | sort | comm -23 ./setvars - 

To break this down a bit, here is what it does:

  1. set | grep "^\([[:alnum:]]\|[[:punct:]]\)\+=" | sort > ./setvars : This hopefully gets all of the variables (as discussed before), sorts them, and sticks the result in a file.
  2. && env | sort: After the previous command is complete, we are going to call env and sort its output.
  3. | comm -23 ./setvars -: Finally, we pipe the sorted output of env into comm and use the -23 option to print the lines that are unique to the first argument, in this case the lines unique to our output from set.

When you are done you might want to cleanup the temp file that it created with the command rm ./setvars

1
  • The problem with your command is not portability (it's specific to bash, of course, but there's no problem on the grep side). The problem is that you're grabbing some lines in function definitions. Bash indents most of the function code, but not all of it, in particular not text inside here documents (f () {|cat <<EOF|foo=bar|EOF|} where | represents a line break). Commented Oct 26, 2010 at 19:01
7

Just use the command env. It does not print functions.

1
  • 3
    It does if used inside a script. Commented Sep 5, 2013 at 13:08
3

Try the command printenv:

$printenv
2
  • 9
    printenv and env only print exported (environment) variables and not non-exported (shell) variables.
    – Lri
    Commented Jun 5, 2013 at 7:36
  • @Lri Thanks! I wondered how to print out just the exported variables. Commented Oct 2, 2019 at 18:24
3

In bash, this will print only variable names:

compgen -v

Or, if values are needed also, use:

declare -p
1
1

diffing against a clean shell to ensure also vars from rc scripts get left out

## get mostly local vars
diff_env(){
    diff <(bash -cl 'set -o posix && set') \
        <(set -o posix && set && set +o posix) | \
        grep -E "^>|^\+" | \
        grep -Ev "^(>|\+|\+\+) ?(BASH|COLUMNS|LINES|HIST|PPID|SHLVL|PS(1|2)|SHELL|FUNC)" | \
        sed -r 's/^> ?|^\+ ?//'
}

the version with comm would be too convoluted

1

The accepted answer is great. I'm offering an alternative that doesn't involve setting POSIX mode:

Here's the technique I've been using to store function state to a file so I can continue it later. The first produces a state dump and the second produces just a list of the variable names.

set 2>/dev/null | while read a; do [[ $a == *=* ]] || break; echo $a; done 

set 2>/dev/null | while read a; do [[ $a == *=* ]] || break; echo ${a/=*}; done 

Both work by dumping lines until it finds one without an '=' char, which occurs when the first function is listed. The latter crops the assignment off.

You must log in to answer this question.

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