After reading the manual, it is indeed undocumented that DEBUG, RETURN and ERR have the same semantics for `source' and function calls. Or perhaps it's documented, but I didn't take the time to read it all.
There information related to DEBUG is scattered across man bash. Some of the relevant stuff: >From `FUNCTIONS', second paragraph: All other aspects of the shell execution environment are identical between a function and its caller with these exceptions: the DEBUG and RETURN traps (see the description of the trap builtin under SHELL BUILTIN COMMANDS below) are not inherited unless the function has been given the trace attribute (see the description of the declare builtin below) or the -o func‐ trace shell option has been enabled with the set builtin (in which case all functions inherit the DEBUG and RETURN traps), and the ERR trap is not inherited unless the -o errtrace shell option has been enabled. >From the description of the `set' builtin (same as functrace): -T If set, any traps on DEBUG and RETURN are inherited by shell functions, command substitutions, and commands executed in a subshell environment. The DEBUG and >From the description of the `trap' builtin: If a sigspec is EXIT (0) the command arg is executed on exit from the shell. If a sigspec is DEBUG, the command arg is executed before every simple command, for command, case command, select command, every arithmetic for command, and before the first command executes in a shell function (see SHELL GRAMMAR above). Refer to the description of the extdebug option to the shopt builtin for details of its effect on the DEBUG trap. If a sigspec is RETURN, the command arg is executed each time a shell function or a script executed with the . or source builtins finishes executing. That's all the documentation I could find regarding this. Now, what really happens: Take: dualbus@yaqui ~ % bash -c 'trap "echo bar" DEBUG; source /dev/fd/0; :' <<< 'trap "echo foo" DEBUG; :' bar foo bar Here, the first `bar' is executed before `source'. Then, we enter a new `scope', or whatever this is called. Then, there's `foo', due to the `:' called inside the sourced script. After we `return' from the sourced script, `bar' is written, before the `:' from the main script (the -c one). So, essentially, we have two DEBUG traps set at the same time, living in different scopes. (DEBUG, RETURN and ERR are the only ones special here, I think). And dualbus@yaqui ~ % bash -Tc 'trap "echo bar" DEBUG; source /dev/fd/0; :' <<< 'trap "echo foo" DEBUG; :' bar bar foo foo Now, here we're using -T, so we essentially removed that `scoping' thing, or whatever, making DEBUG a single global trap. The output is due to: bar: before `source' bar: before `trap' inside the sourced script foo: before `:' inside the sourced script foo: before `:' inside the main script (-c) So, from what you can see, there's a pretty convincing and consistent explanation of what bash is doing. Now, is this documented properly? I do not think so. IMO, we should include a paragraph in the description of the `source' builtin that it behaves similarly to functions in terms of traps. (IIRC, `return' already documents this similarity). We should also update the description of the set -T option (and perhaps explain the similarity in the FUNCTIONS section). I'll prepare a patch to the documentation later if I have the time :-) -- Eduardo Bustamante https://dualbus.me/