It seems you had some problems with the solution that uses compgen -A variable
. It might be because of set -x
you mentioned in the question body, I don't know.
Without compgen
now. Let's start with this comment. It says:
grep -vxF -f <( declare -x -p ) <( declare -p )
I partially improve it to
grep -vxF -f <( declare -x -p | grep -o '[^ =]*=') <( declare -p | grep -o '[^ =]*=')
There are few problems now:
A variable that contains =
in its content will make the above code report false positives. Example:
foo='123
bar=baz
=xyz'
Run the code. It will print (among others) foo=
, bar=
and =
. To fix this we should test each line if such variable really exists.
Or we can get duplicates:
foo='123
aaa foo=456
bar=baz
bar=qux'
To fix this we can use sort -u
. I prefer LC_COLLATE=POSIX sort -u
to get lowercase variables at the end.
The above foo
, if exported, can "mask" legitimate (not exported) bar
. Example:
export foo
bar=999
Now the code will not print bar=
. To fix this we should not exclude basing on "global" declare -x -p
. For each variable we should separately check if it's not exported. This step makes the preliminary grep -vxF
unnecessary.
So this:
declare -p | grep -o '[^ =]*=' | LC_COLLATE=POSIX sort -u \
| while IFS='=' read -r v ; do
[ -v "$v" ] \
&& bash -c "[ ! -v '$v' ]" \
&& printf '%s\n' "$v"
done
Note I do not strictly check if a variable is exported. I check if it is set in a child bash
. There are variables specific to interactive shell that will be reported, you may want to filter them out manually. Or you can run bash -ic …
; but multiple times, so it will be even slower.
Since we have a reliable(?) way to get a list of all variables without false positives, we can go back to the original idea of grep -vxF
and only run a child bash
once. The following snippet works quite well with bash -i
. In my case the only variables to manually filter out are COLUMNS
and LINES
.
( set +x
function _get_vars {
declare -p | grep -o '[^ =]*=' | LC_COLLATE=POSIX sort -u \
| while IFS='=' read -r v ; do
[ -v "$v" ] \
&& printf '%s\n' "$v"
done
}
export -f _get_vars
_get_vars | grep -vxF -f <( bash -ic '_get_vars' ) \
| grep -vE '^(COLUMNS|LINES)$' )
I deliberately use set +x
for the subshell just in case; the subshell is also useful to keep the function "local" (it will not be available in your current shell). You may prefer a non-interactive child bash and more filtering. Adjust this to your needs.
Maybe, just maybe set +x
in the right place will make the compgen
approach work for you. How about this?
( set +x
compgen -A variable | grep -vxF -f <( bash -ic 'compgen -A variable' ) \
| grep -vE '^(COLUMNS|LINES)$' )
Disclaimer: not tested in Bash 3.2.52; tested in 4.4.12.
grep -vxF -f <( declare -x -p ) <( declare -p )
. It's unreliable whenever there are variables containing newlines (which there almost always are). Also does not work with the stockbash
on macOS as the lines in the output ofdeclare -p
does not start withdeclare
(while the ones fromdeclare -x -p
do).