In bash, which traps are inherited by functions and builtins?
Asked Answered
R

2

7

I'm not really clear on which traps are inherited when in bash. The manpage says:

When a simple command other than a builtin or shell function is to be executed, it is invoked in a separate execution environment [...] [T]raps caught by the shell are reset to the values inherited from the shell's parent, and traps ignored by the shell are ignored[.]

Later on it says that subshells behave the same way.

This makes enough sense to me, I think. It sounds like if a shell unsets a trap, that gets remembered by any subshells and whatnot, while if a shell sets a trap, that gets forgotten. I don't get why that design choice was made, but I at least think I understand what's going on.

But what about commands that are builtins or shell functions? And do the two ever behave differently (with respect to trap inheritance)? I can't find a comprehensive description in the manual--as far as I can tell, anything goes. It seems like they are usually inherited from the parent, but there are exceptions like ERR and RETURN, each of which are only inherited by functions when certain shell options are being used.

My questions are:

  • What exactly is the typical way trap inheritance works for builtins and functions? E.g., are there any subtleties regarding setting vs. unsetting traps, the way there appears to be with most commands?

  • Do all functions and builtins behave the same way? (Please don't tell me each builtin has a separate set of rules...)

  • What are the exceptions? I.e., what signals behave differently by default, and what functions can have their default behavior changed by shell options and whatnot? I know about ERR and RETURN, but are there any others? Try as I might, I couldn't find a nice simple list of this anywhere.

  • When, if ever, can a function or builtin affect the traps of a parent? How does trap - SIGSPEC vs. trap '' SIGSPEC play into this?

Thanks!

PS: I am using GNU bash version "4.4.19(1)-release".

Rozier answered 9/4, 2018 at 1:6 Comment(0)
H
3

The original post contain multiple questions. Trying to address the last question: When, if ever, can a function or builtin affect the traps of a parent? How does trap - SIGSPEC vs. trap '' SIGSPEC play into this? (2 questions)

When can a function affect traps of a parent ?

Short Answer: Only functions running in the same process as the parent caller can impact the caller.

Long Answer: It is important to note that signals are specific to 'execution environment'. In linux/Unix this is a process. The inheritance happens between processes. Therefore a function can impact the 'parent' (the caller) only if executed in the same process. It will not be able to impact the 'parent' if executed in a different process. Examples for using a sub-shell are the when a sub-shell is forced (using '(command)'), when using a pipeline (with the exception of the last command of the pipeline), when using command substitutions, and more.

function f {
   trap 'echo SIGNAL' TERM
}

# Will impact current process traps, as 'f' is executed in the same process
f
# Will not impact current process traps, as 'f' is executed in a sub-shell.
(f)

Difference between 'trap - SIG' and 'trap "" sig':

Short Answer: The '-' will restore signal behavior, "" will ignore it. For most signals, the default behavior is to terminate the shell.

Long Answer: Using 'trap - SIG' will reset the signal to the default behavior, whereas 'trap "" SIG' will force the signal to be ignored.

For (few) signals the default (initial) behavior is to be ignored (e.g. QUIT), therefore, there is no difference between the commands. For most other signals (e.g., QUIT), the default behavior is to ignore the signal. You can use 'trap "" SIG' to prevent the signal from having impact during specific point of time.

For example: Disallow ctrl/C (INTR) during copying of critical files:

sleep 5
# Disable INT during copying of critical files
trap '' INT
cp -r critical-file backup
# Restore INT behavior
trap -- INT

Since signals are inherited, the 'default' behavior of a signal can be modified. Consider:

# Get greeting on USR1
trap 'echo HI' USR1
# Start sub-shell
(
# USR1 will result in 'HI'
sleep 5
trap '' USR1
# USR1 will be ignored during critical file copy
cp -r critical-file backup
# Restore INT behavior
trap - INT
# USR1 will result in 'HI'
sleep 5
)
Hispid answered 3/10, 2019 at 11:51 Comment(0)
H
2

Attempting to address:

What exactly is the typical way trap inheritance works for builtins and functions? E.g., are there any subtleties regarding setting vs. unsetting traps, the way there appears to be with most commands?

As noted above, functions and built-in do not inherit traps. Inheritance works over a process boundaries (sub shell, pipelines, command substitution, etc). When a function or builtin command is executed in the same process, it is sharing the same trap setting as the caller - it can even modify them.

From Bash Manual (FUNCTIONS section), the following taps are special for functions: DEBUG, RETURN and ERR.

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 functrace 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

builtin works in similar way - but the RETURN, DEBUG do not apply to single commands. The ERR applies to built-in as well.

Deferred Signal Processing:

Worth highlighting the most scripting engines will usually use 'deferred processing' for signal handling. When a signal is received, the shell will remember the signal, set the 'abort ASAP' flag, then resume current command execution. The command will attempt to terminate ASAP, when it examines the 'abort asap'.

For built-in commands, which execute very quickly, possible to complete execution without checking the 'abort ASAP'. For functions the scripting engine will usually examine the 'abort ASAP' flag after the completion of each "simple" step.

The deferred execution allow each command to perform normal cleanup (release memory and other resources).

The execution of the signal command (set by the trap) is initiated only when the current executing command is terminated.

Hispid answered 3/10, 2019 at 16:37 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.