One approach is to override export
and unset
with functions that keep track of your variables.
Please notice that for this to work you must export variables like this: export <variable>=<value>
.
The two functions must be called export
and unset
and maintain a list of your variables in a file, which I'll name ~/.track$$
($$
is the PID of your current shell):
export ()
{
if echo $@ | egrep -q '[^=]+=[^=]+' && builtin export "$@" && echo $- | grep -q i; then # [1]
touch ~/.track$$ # [2]
cp -fa ~/{.track$$,.track$$.bak} 2> /dev/null; # [3]
grep -v "^$(echo $@ | sed 's/\([^=]=\).\+/\1/')" < ~/.track$$.bak > ~/.track$$ 2> /dev/null; # [4]
echo $@ >> ~/.track$$; # [5]
fi
}
unset ()
{
if builtin unset $@ && echo $- | grep -q i; then # [1]
touch ~/.track$$ # [2]
cp -fa ~/{.track$$,.track$$.bak} 2> /dev/null; # [3]
egrep -v "^$@=" < ~/.track$$.bak > ~/.track$$ 2> /dev/null; # [4]
fi
}
Additionally you need to find out which environment variables you have set. An alias like this will make it:
alias iset="cat ~/.track$$ 2>/dev/null"
An alias like this:
alias ireset="rm ~/.track$$ >/dev/null 2>&1"
can be used to reset the list.
To keep the number of ~/.track$$
files to a minimum we can use this housekeeping command:
rm $(ls -d ~/.* | egrep 'track[0-9]+$|track[0-9]+\.bak$' | egrep -v $(ps -ef | grep bash | grep -v grep | awk '{print $2}' | tr '\n' '|' | sed 's/|$//')) > /dev/null 2>&1
Where should we to put these functions, aliases, etc?
I'd would recommend that you add the functions, aliases and the housekeeping command to the end of ~/.bashrc
, so that subshells get the functions too. Depending on your distro and how /etc/profile
, /etc/bash.bashrc
and friends are executed you may define them too early (in some distros ~/.profile
sources ~/.bashrc
or the other way round) so you may have to fine tune it.
Then log out and log in again.
Now, if you run export <variable>=<value>
in your bash session the function export
:
[1] checks whether the parameter is well formed (this is to avoid spurious entries when typing export MYVAR
, because its return value is 0 and would create an entry in ~/.track$$
)
[1] then it executes builtin export <variable>=<value>
(this sets the variable, regardless of what happens afterwards)
[1] then it greps $-
to see whether this is an interactive shell (see this answer When I ssh into a ubuntu machine, what kind of shell am I using [shameless plug]). By default, subshells don't inherit functions, so scripts won't create ~/.track$$
files. If a script uses #!/bin/bash -i
, though, it will. This will prevent it.
[2] then it touches ~/.track$$
to make sure it exists and
[3] makes a backup copy
[4] then it checks whether the variable already exists, if that's the case it is deleted from ~/.track$$
[5] finally, it adds a line to ~/.track$$
Similarly, if you type unset <variable>
and hit Enter the function unset
:
[1] executes builtin unset <variable>
(this unsets the variable, regardless of what happens afterwards)
[1] if unset
was successful it checks, as in the export
function above. whether this is an interactive shell (see this answer When I ssh into a ubuntu machine, what kind of shell am I using [shameless plug]). By default, subshells don't inherit functions, so scripts won't create ~/.track$$
files. If a script uses #!/bin/bash -i
, though, it will. This will prevent it.
[2] then it touches ~/.track$$
to make sure it exists and
[3] then makes a backup copy of ~/.track$$
[4] removes the variable entry from ~/.track$$
What is the builtin
keyword I used in both functions? Since export
or unset
are shell builtins (that is, commands that come with the shell) I need to use builtin
, which is itself a builtin command (from man bash
):
builtin shell-builtin [arguments]
Execute the specified shell builtin, passing it arguments, and return its exit status. This is useful when defining a function whose name is the same as a shell builtin, retaining the functionality of the builtin within the function. The cd builtin is commonly redefined this way. The return status is false if shell-builtin is not a shell builtin command.
The housekeeping command lists and filters the active ~/.track$$
files out and deletes the rest.
What you get with this setup:
No need to use underscores anymore. Simply use the familiar export
command (which has been overriden with a function) and it will work.
Each bash
session has its own ~/.track$$
file. No collisions among shells.
As side effect from the above, subshells don't inherit the parent's ~/.track$$
file, although they inherit all environment variables.
Environment variables set in sourced files (. file
or source file
) are added to ~/.track$$
.
Environment variables set in a () subshell are also (incorrectly) tracked, because $$
expands to the process ID of the current shell, not the subshell (see man bash
).
Variables are listed from first to last exported.
Variables set by .profile
and other scripts that you reassign (even with the same value) will be listed by iset
. For instance, HOME
is usually set by .profile
. If you do export HOME=/usr/local/bin:$HOME
and run iset
you will see HOME
listed.
And now some examples:
Exported variables are shown with iset
:
$ export MYVAR1=0987654321; iset
MYVAR1=0987654321
Redefined variables are dealt with properly:
$ export MYVAR2="this is a string"; iset
MYVAR1=0987654321
MYVAR2=this is a string
$ export MYVAR2="this is a different string for the same variable"; iset
MYVAR1=0987654321
MYVAR2=this is a different string for the same variable
Environment according to env
matches iset
output:
$ env|grep MYVAR
MYVAR2=this is a different string for the same variable
MYVAR1=0987654321
Readonly variables are not added to ~/.track$$
:
$ export EUID=0; iset
-bash: EUID: readonly variable
MYVAR1=0987654321
Old, no longer used track files are deleted when ~/.bashrc
is executed (for instance, when creating a subshell):
$ ls -1 ~/.track*
.track11002
.track11002.bak
.track21774
.track21774.bak
.track2923
.track2923.bak
.track7382
.track7382.bak
.track8374
.track8374.bak
$ echo $$
2923
$ bash
<subshell>$ ls -1 ~/.track*
.track2923
.track2923.bak
You're welcome, I'm glad I could help. – jaume – 2012-11-06T07:29:15.440
Your approach can be defeated in 3 ways: 1. Unset a var which was exported by .profile. (the change won't be shown) 2. Unset and re-export (to original value) a var which was exported by .profile. (it will be incorrectly listed by
iset
) 3.export myvar; myvar=value
(iset
will show the wrong value). – Hugh Allen – 2012-11-06T13:29:05.903@Hugh Allen: I agree with 3.,
export var=value
must be used, I edited my answer to make it clearer, thanks for perusing it and pointing that out. As of 2., if I understand correctly, the OP wishes to find out environment vars that are set (ie, that are assigned a value) after logging in, so any var set afterwards must be tracked. Consequently, vars exported by.profile
, if set later, are added toiset
. Regarding 1., I don't see how a var can be deleted fromiset
's output if it was never added to it. 1. is desired behavior: vars set by.profile
and not reassigned aren't monitored. – jaume – 2012-11-07T06:57:46.307You might be right. I had assumed Yordan wanted to know about any changes including variables which were unset, as per his second approach using a comparison command. – Hugh Allen – 2012-11-07T10:51:17.487