Reputation: 48824
I have a script that I source in my .bashrc
which sets the DEBUG
trap. This works fine, normally, but I recently discovered that if the DEBUG
trap has previously been set (e.g. earlier in the .bashrc
) the sourced trap
call doesn't persist - it appears to be re-overwritten by the earlier trap.
Isolated Example:
$ env -i bash --noprofile --norc
bash-4.3$ trap
trap -- '' SIGTSTP
trap -- '' SIGTTIN
trap -- '' SIGTTOU
bash-4.3$ cat /tmp/echotrap.sh
#!/bin/bash
trap 'echo "DEBUG TRAP TRIGGERED"' DEBUG
bash-4.3$ source /tmp/echotrap.sh
bash-4.3$ true
DEBUG TRAP TRIGGERED
bash-4.3$ trap
DEBUG TRAP TRIGGERED
trap -- '' SIGTSTP
trap -- '' SIGTTIN
trap -- '' SIGTTOU
trap -- 'echo "DEBUG TRAP TRIGGERED"' DEBUG
bash-4.3$ exit
DEBUG TRAP TRIGGERED
exit
Notice that the DEBUG
trap is set by sourcing /tmp/echotrap.sh
, as I expected.
However if the DEBUG
trap has already been set, this no longer works:
$ env -i bash --noprofile --norc
bash-4.3$ trap
trap -- '' SIGTSTP
trap -- '' SIGTTIN
trap -- '' SIGTTOU
bash-4.3$ trap : DEBUG
bash-4.3$ trap
trap -- '' SIGTSTP
trap -- '' SIGTTIN
trap -- '' SIGTTOU
trap -- ':' DEBUG
bash-4.3$ source /tmp/echotrap.sh
bash-4.3$ true
bash-4.3$ trap
trap -- '' SIGTSTP
trap -- '' SIGTTIN
trap -- '' SIGTTOU
trap -- ':' DEBUG
bash-4.3$ exit
exit
The initial trap remains.
Is there any workaround for this issue (from within the sourced script)? I'd like to be able to update the DEBUG
trap regardless of its current state, not only if it hasn't been set before.
Upvotes: 2
Views: 612
Reputation: 4232
From the Bourne Shell Builtins page, in the .
section:
If the -T option is enabled, source inherits any trap on DEBUG; if it is not, any DEBUG trap string is saved and restored around the call to source, and source unsets the DEBUG trap while it executes. If -T is not set, and the sourced file changes the DEBUG trap, the new value is retained when source completes.
So, to make the trap stick, in the bash that source
s your file (not in the source
d file itself), do set -T
before source
ing.
Then your tests should pass.
However, this doesn't meet your stretch goal of being able to ensure that traps get installed within the source
d script, without requiring the invoking user to set an option. Perhaps this is considered to be a security feature.
Regardless, whether or not the source
d file does set -T
itself, it only persists for the "scope" of the sourced file. This is true even if the source
d file uses the $BASH_SOURCE
variable to re-source
itself a second time after setting -T
. This implies the presence of some nested scope-like behavior in Bash that I wasn't previously familiar with; if those layered set/not-set behaviors persist to arbitrary depth in source
d files, that could potentially be abused in interesting ways. Regardless, you can at least inform the user source
ing your file that your traps probably won't work, by putting some code like this at the top of the file:
if [[ ! $- =~ T ]]; then
echo "Can't install traps unless '-T' option is enabled; not loading library"
return
fi
trap 'echo "DEBUG TRAP TRIGGERED"' DEBUG # Won't run unless -T has been enabled
Another option, if you don't want to place the burden on source
d libraries to check for that sort of thing, is to override source
itself with a function which conditionally overrides trap
. Since function definitions are available to source
d files, something like this would work:
function source() {
if [[ ! $- =~ T ]]; then
function trap() {
if [[ $@ == *DEBUG ]]; then
echo "Debug traps won't work in a source/.'d file unless the shell option -T is set before calling source/."
return 1
else
builtin trap $@
fi
}
fi
builtin source $@
unset trap
}
The function can't check whether the DEBUG
trap is set before deciding to override trap
, however; the same behavior that makes DEBUG
traps provisionally invisible to source
d code also makes them invisible inside functions. To work around that, you could pack the entire source
function into an alias, which does have access to DEBUG
traps, at the cost of readability (e.g. alias source='if [[ ! $- =~ T && $(trap -p DEBUG) ]]; then echo SORRY; fi; builtin source'
).
This override can also be "defeated" by code that uses builtin source
or builtin type
directly.
Upvotes: 3