57

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

51

"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
  • 1
    By far the best solution – Ruslan May 25 '14 at 9:27
  • 1
    There are a bunch of usually uninteresting variables starting with _, which are easy to get rid of: (set -o posix ; set | grep -v ^_) – hyde Nov 24 '14 at 15:34
  • This has been bothering me too for so long! Grepping away the lines that start with _ isn't enough if you have multi-line values, it will still leave the contents of the value in tact. Since set prints the lines in order, all the _ lines will be at the end, so you can simply cut the results after the first _ line, using: set -o posix; set | sed -e '/^_/,$d'; set +o posix; – Scott Apr 6 '15 at 21:52
  • 1
    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 Sep 29 '16 at 9:37
  • 1
    Side effects: Sets POSIXLY_CORRECT=y and adds posix to $SHELLOPTS – Tom Hale Apr 16 '18 at 8:18
31

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.

  • 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 May 12 '11 at 14:29
  • +1 for compgen :) – RSFalcon7 Mar 6 '14 at 23:55
13

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
  • Great solution! I didn't even think of just stopping at the first line that doesn't have a '='. +1 – Steven D Oct 27 '10 at 3:34
  • Equivalently, with sed: set | sed '/=/!Q' – Toby Speight Jul 5 '18 at 17:18
7

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

  • 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). – Gilles Oct 26 '10 at 19:01
6

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

  • 1
    It does if used inside a script. – DocSalvager Sep 5 '13 at 13:08
2

Try the command printenv:

$printenv
  • 6
    printenv and env only print exported (environment) variables and not non-exported (shell) variables. – Lri Jun 5 '13 at 7:36
  • @Lri Thanks! I wondered how to print out just the exported variables. – Mark Stewart Oct 2 at 18:24
1

In bash, this will print only variable names:

compgen -v

Or, if values are needed also, use:

declare -p
0

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

0

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.

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.