4

Lets supose some bash shell script like:

#!/bin/bash 
a=3
b=7
set -o posix
set

In this case, the execution of the set variable or some other of the commands to print environment variables (env,declare... etc) outputs my a and b variables, plus a lot of variables (and functions) from the environment (PATH, PWD, TEMP... etc).

Is there a way to print only those variables that have been defined during the script body?

1
  • You probably want to filter against the export attribute. Look harder at typeset, probably it can do this, but I don't believe there is a posix portable method of doing so. Of course the simple solution is to track the variables you set in your script ... in your script... If your parent shell is bash, by the way, that would explain all of your unwanted env. some more simple shells will not have this issue - especially if invoked like env - script.
    – mikeserv
    Dec 9, 2014 at 8:09

6 Answers 6

1

You can store the list of variables at the beginning of the script and compare it with the value somewhere during the script. Beware that the output of commands like set isn't built to be unambiguous: something like set | sed 's/=.*//' doesn't work with variables whose values contain newlines. (In bash, it actually does for string variables, but not for arrays, and it also displays function code.) I think that the following snippet reliably lists the currently defined variables (in alphabetical order, to boot):

variables=$(tmp=$(declare -p +F); declare () { echo "${2%%=*}"; }; eval "$tmp")

Thus, set initial_variables=… at the beginning of the script, and compare with the later value. You can use something other than echo to act on the list directly.

initial_variables=" $(tmp=$(declare -p +F); declare () { echo "${2%%=*}"; }; eval "$tmp") "
…
( tmp=$(declare -p +F)
  declare () {
    case "$initial_variables" in
      *" $2 "*) :;; # this variable was present initially
      *) eval "set -- \"\$$2\" \"\$2\""; echo "locally defined $2=$1";;
    esac
  }
)
1

I cheated: I simply renamed the variables that I didn't want listed to something that begins with the letters "aaa" (for example the variables that I sourced form another file), and I then called the builtin compgen -v command. Then I sorted by the number of variables that I knew existed in my script.

For instance, I sourced from another file two variables who's names started with aaa, and I had three randomly named variables in my script (first letter ranged anywhere from A to Z), and I was able to list only those three variables in the output by putting this simple command in my script:

compgen -v | tail -n 3

EDIT:
if I didn't know exactly how many variables I had in my script: then I could do something like this:

compgen -v | awk '$0 == "aaauser" {i=1;next};i && i++ <= 100'

where aaauser is the last alphabetically ordered variable that I sourced from my other file. You could apply that to any known variable.

1

A 2 line strategy using comm, compgen -v and Process Substitution...

ENVVARS="$(compgen -v)"
    :
  <script>
    :
comm -1 -3 <(sort <<< "$ENVVARS") <(compgen -v | sort)

From comm --help...

Usage: comm [OPTION]... FILE1 FILE2

Compare sorted files FILE1 and FILE2 line by line.

With no options, produce three-column output.  Column one contains
lines unique to FILE1, column two contains lines unique to FILE2, and
column three contains lines common to both files.

    -1              suppress lines unique to FILE1
    -2              suppress lines unique to FILE2
    -3              suppress lines that appear in both files

The comm command allows us to "subtract" the set of environment variables passed to the script from the total set present at the end of the script to leave us with just those created by the script.

Options -1 and -3 discard all but the variables created in this script (which are lines in "file 2"). Bash Process Substitution <(...) is used to list and sort the variables and then treat the output as a file.

Note that variables declared local to functions are not included as they are outside the scope of the main script.

The same technique could be used to list variables created inside a function. However, the convention of naming such variables beginning with the underscore (_) and adding the underscore prefix to the compgen call in such cases may be more desirable...

LOCALVARS="$(compgen -v _)"
1

A solution using comm based on storing all the variables before and after:

#!/bin/bash 

# Storing variables before:
set -o posix
set > $TMP/VariablesBefore.txt
sort $TMP/VariablesBefore.txt -o $TMP/VariablesBeforeSorted.txt

a=3
b=7

# Storing variables after:
set > $TMP/VariablesNow.txt
sort $TMP/VariablesNow.txt -o $TMP/VariablesNowSorted.txt

# Computing differences:
echo "The variables inside the script are: "
comm -3 $TMP/VariablesBeforeSorted.txt $TMP/VariablesNowSorted.txt

Well, there are still a few variables remaining like _ or BASH_LINENO, but this is the bet I have reached.

Note that this solution could not work in all cases, for example variables very long (KB), with new line codes, or variables including variable names. Thanks, MikeServ, for pointing.

2
  • I have tested it working with variables containing numbers, strings and pathnames. What do you mean by "working with names"? Dec 9, 2014 at 8:27
  • Uh... no, they are just numbers and strings. It could be interesting to solve those cases you tell, @mikeserv. Dec 9, 2014 at 9:10
0

Here's a portable means of getting a sorted list of all currently defined varnames:

v(){ set "${IFS+IFS=\$2}" "$IFS"; unset IFS
     set  $(set|sed -n '/^[_[:alpha:]][[:alnum:]]*=/s/=.*//p'|sort -u) "$@"
     while [ "$#" -gt 2 ] 
     do    eval '[ "${'$1'+1}" = 1 ]' && 
           echo "$1"; shift
     done; eval "$1"
}

That list is echoed to v()'s stdout - one name per line. Because it is sorted it is a good candidate for later comparison with a similarly generated list as you might do with comm or similar. The list intentionally excludes $IFS - and this is because - set or not - $IFS is always in effect and so must be a given anyway in such a list. That could be altered like:

set "${IFS+IFS=\$2}" "$IFS"; IFS='
'; set $(set|... 

...but that would only always include it in a list whether it were set or not - which brings me back to my previous point...

0

Bash shell will load /etc/profile, ~/.bash_profile, ~/.bash_login, ~/.profile in the order.

Anything that is not defined in above can be safely assumed to be set by your script.

You must log in to answer this question.

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