Trap: Difference between revisions

From NovaOrdis Knowledge Base
Jump to navigation Jump to search
 
(42 intermediate revisions by the same user not shown)
Line 7: Line 7:
* [[Bash#Built-In_Commands|bash]]
* [[Bash#Built-In_Commands|bash]]
* [[Handling Signals in bash]]
* [[Handling Signals in bash]]
* [[Linux Signals]]


=Overview=
=Overview=


Trap is a facility to instruct bash to catch signals and execute code depending on the signal. A common usage in shell scripts is to prevent those scripts to exit untimely when users type keyboard abort sequences, but run cleanup code instead.
"trap" statement is a bash facility that allows for code execution upon catching specific signals. A common usage in shell scripts is to prevent a script to exit untimely when the user types a keyboard abort sequences, but run cleanup code instead. When using to execute code on script exit, conceptually the trap facility is similar to the Java "finally" construct. However, trap can be used in situation that do not involve the script exiting - catching Ctrl-C and discarding it, specifically to prevent script exit, is one of those. When bash receives a signal for which a trap has been set while waiting for a command to complete, the trap will not be executed until the command completes. When bash is waiting for an asynchronous command via the [[Wait|wait]] built-in, the reception of a signal for which a trap has been set will cause the "wait" built-in to return immediately with an exit status greater than 128, immediately after which the trap is executed.


Example:
=Syntax=


trap 'rm -f ./lock' EXIT
<syntaxhighlight lang='bash'>
trap <commands> <signals>
</syntaxhighlight>


Global variables declared before the trap declaration are correctly resolved when present in a single-quote quoted string (even if single-quotes are used, the single quote semantics when used in bash command line is different from that in effect here). For example, the following code:
One or more signals can be listed:
<syntaxhighlight lang='bash'>
<syntaxhighlight lang='bash'>
a=hello
trap 'echo Ctrl-C has been pressed' SIGINT
trap 'echo ${a}' EXIT
</syntaxhighlight>
</syntaxhighlight>
produces:
 
<syntaxhighlight lang='text'>
The signals can be listed with or without "SIG" prefix (SIGKILL and KILL are equivalent) or using their numeric correspondents:
hello
<syntaxhighlight lang='bash'>
trap "rm -f /tmp/blah" 0 2 3 5 10 13 15
</syntaxhighlight>
</syntaxhighlight>
Variable resolution is done at the time of execution, not declaration, so the following:
 
Aside from the standard [[Linux_Signals|Linux signals]], bash comes with a [[#Special_Bash_Signals|special set of signals]] of its own:
<syntaxhighlight lang='bash'>
<syntaxhighlight lang='bash'>
trap 'echo ${a}' EXIT
trap 'rm -f ./lock' EXIT
a=hello
</syntaxhighlight>
</syntaxhighlight>
also produces:
 
<syntaxhighlight lang='text'>
=Special Bash Signals=
hello
==EXIT==
"EXIT" is not a Linux signal, but a bash psuedo-signal, which is executed when the script exits. It can be used to make sure that the script executes some cleanup on exit.
==DEBUG==
For DEBUG, the list of commands is executed after every simple command in the script. [[Bash_declare/typeset#-t|Traced functions]] inherit the DEBUG traps from the calling shell.
 
==ERR==
The trap commands are executed every time a script command exits with a non-zero exit status, unless the non-zero exits status comes from part of an "if" statement or from a "while" or "until" loop, or if a logical AND (&&) or OR (||) expression results in a non-zero exit code.
==RETURN==
[[Bash_declare/typeset#-t|Traced functions]] inherit the RETURN traps from the calling shell.
 
=trap Executable Section=
 
The executable section - the sequence of commands to be executed when the signal is caught - must be enclosed in single or double quotes. The type of quotes is relevant to how the variables specified there are expanded. See [[#Shell_Variables_and_Executable_Section|Shell Variables and Executable Section]] for more details.
 
<syntaxhighlight lang='bash'>
trap "echo bye" EXIT
</syntaxhighlight>
</syntaxhighlight>


<font color=darkgray>
Experiment with local variables. This works, explain this:
</font>
<syntaxhighlight lang='bash'>
<syntaxhighlight lang='bash'>
local chart_dir=blah
trap 'echo bye' EXIT
trap 'rm -rf '${chart_dir}'/tmpcharts; echo chart dir: ${chart_dir}' EXIT
</syntaxhighlight>
</syntaxhighlight>
<font color=darkgray>
 
produces:
Multiple commands can be specified with the following syntax
</font>
<syntaxhighlight lang='bash'>
<syntaxhighlight lang='text'>
trap "{ rm -r /tmp/lock; exit 255; }" EXIT
chart dir: blah
</syntaxhighlight>
</syntaxhighlight>


<font color=darkgray>
==Shell Variables and Executable Section==
This seems to work for local variables:
 
</font>
"..." and &#39;...' maintain their [[Bash_Parameter_and_Variable_Expansion#Overview|semantics relative to variable expansion]]: if the executable section is enclosed in double quotes, the variables specified there will be expanded when the trap is registered. If the executable section is enclosed in single quotes, the variables will not be expanded when the trap is registered, but when the executable code is actually executed, upon signal catch.
<syntaxhighlight lang='text'>
<syntaxhighlight lang='bash'>
trap 'rm -rf '${tmp_dir}' && debug '${tmp_dir}' removed; rm -rf '${chart_dir}'/tmpcharts && debug '${chart_dir}'/tmpcharts removed' EXIT
COLOR="blue"
trap "echo ${COLOR}" EXIT
COLOR="green"
</syntaxhighlight>
will print "blue" upon script exit, while:
<syntaxhighlight lang='bash'>
COLOR="blue"
trap 'echo ${COLOR}' EXIT
COLOR="green"
</syntaxhighlight> will print "green" upon script exit, because the variable declaration ${COLOR} is preserved without expansion until the moment the executable sequence is actually executed on exit. To enable variable expansion at the time of declaration when single quotes are used, use this syntax:
<syntaxhighlight lang='bash'>
tmp_dir="/tmp/something"
trap 'rm -rf '${tmp_dir}' && debug '${tmp_dir}' removed' EXIT
</syntaxhighlight>
</syntaxhighlight>


=Special Bash Signals=
==trap and Functions==
 
The execution sequence can be a function:
 
<syntaxhighlight lang='bash'>
trap do-something EXIT


"EXIT" in the example above is not a Linux signal. Bash provides this psuedo-signal, which is executed when the script exits; this can be used to make sure that your script executes some cleanup on exit.
function do-something() {
  echo "exiting ..."
  return 10 # does not influence the exit value of the calling script
  # exit 12 # will replace the exit value of the calling script
}
</syntaxhighlight>


Other bash pseudo-signals:
The function can be declared after the trap registration in the script - trap will capture the function name and it will attempt to execute it when it is triggered by its signal. If the function can be found then, it will be executed correctly.
* DEBUG
* RETURN
* ERR


For a list of signals that can be handled, see: {{Internal|Linux_Signals|Linux Signals}}
If the function is executed upon script exit, its return value, if the function does not call <code>exit</code>, does not influence the exit value of the script. However, if the function invokes <code>exit</code>, then the value <code>exit</code> is invoked with will change the exit value of the script. This logically makes sense, as the trap function code is the last that executes.
Also see: {{Internal|Handling Signals in bash|Handling Signals in bash}}


=Only One Code Sequence (Latests) Executes=
=Only One Code Sequence (Latests) Executes=


If multiple code sequences are declared with <code>trap</code>, only the last one is executed. The following example:
If multiple code sequences are declared with <code>trap</code> for the same signal, only the last one is executed. The following example:


<syntaxhighlight lang='bash'>
<syntaxhighlight lang='bash'>
Line 104: Line 135:
<syntaxhighlight lang='bash'>
<syntaxhighlight lang='bash'>
local tmp_dir
local tmp_dir
local preserve_tmp_dir=false
tmp_dir=$(get-tmp-dir) || exit 1
tmp_dir=$(get-tmp-dir) || exit 1
trap "rm -rf ${tmp_dir} && debug deleted temporary directory ${tmp_dir} || warn failed to delete temporary directory ${tmp_dir}" EXIT && \
if ${preserve_tmp_dir}; then
    debug "temporary directory ${tmp_dir} will be preserved on exit"
else
    trap "rm -rf ${tmp_dir} && debug deleted temporary directory ${tmp_dir} || warn failed to delete temporary directory ${tmp_dir}" EXIT && \
     debug "registered temporary directory ${tmp_dir} cleanup procedure"
     debug "registered temporary directory ${tmp_dir} cleanup procedure"
fi
</syntaxhighlight>
</syntaxhighlight>



Latest revision as of 23:34, 23 July 2020

External

Internal

Overview

"trap" statement is a bash facility that allows for code execution upon catching specific signals. A common usage in shell scripts is to prevent a script to exit untimely when the user types a keyboard abort sequences, but run cleanup code instead. When using to execute code on script exit, conceptually the trap facility is similar to the Java "finally" construct. However, trap can be used in situation that do not involve the script exiting - catching Ctrl-C and discarding it, specifically to prevent script exit, is one of those. When bash receives a signal for which a trap has been set while waiting for a command to complete, the trap will not be executed until the command completes. When bash is waiting for an asynchronous command via the wait built-in, the reception of a signal for which a trap has been set will cause the "wait" built-in to return immediately with an exit status greater than 128, immediately after which the trap is executed.

Syntax

trap <commands> <signals>

One or more signals can be listed:

trap 'echo Ctrl-C has been pressed' SIGINT

The signals can be listed with or without "SIG" prefix (SIGKILL and KILL are equivalent) or using their numeric correspondents:

trap "rm -f /tmp/blah" 0 2 3 5 10 13 15

Aside from the standard Linux signals, bash comes with a special set of signals of its own:

trap 'rm -f ./lock' EXIT

Special Bash Signals

EXIT

"EXIT" is not a Linux signal, but a bash psuedo-signal, which is executed when the script exits. It can be used to make sure that the script executes some cleanup on exit.

DEBUG

For DEBUG, the list of commands is executed after every simple command in the script. Traced functions inherit the DEBUG traps from the calling shell.

ERR

The trap commands are executed every time a script command exits with a non-zero exit status, unless the non-zero exits status comes from part of an "if" statement or from a "while" or "until" loop, or if a logical AND (&&) or OR (||) expression results in a non-zero exit code.

RETURN

Traced functions inherit the RETURN traps from the calling shell.

trap Executable Section

The executable section - the sequence of commands to be executed when the signal is caught - must be enclosed in single or double quotes. The type of quotes is relevant to how the variables specified there are expanded. See Shell Variables and Executable Section for more details.

trap "echo bye" EXIT
trap 'echo bye' EXIT

Multiple commands can be specified with the following syntax

trap "{ rm -r /tmp/lock; exit 255; }" EXIT

Shell Variables and Executable Section

"..." and '...' maintain their semantics relative to variable expansion: if the executable section is enclosed in double quotes, the variables specified there will be expanded when the trap is registered. If the executable section is enclosed in single quotes, the variables will not be expanded when the trap is registered, but when the executable code is actually executed, upon signal catch.

COLOR="blue"
trap "echo ${COLOR}" EXIT
COLOR="green"

will print "blue" upon script exit, while:

COLOR="blue"
trap 'echo ${COLOR}' EXIT
COLOR="green"

will print "green" upon script exit, because the variable declaration ${COLOR} is preserved without expansion until the moment the executable sequence is actually executed on exit. To enable variable expansion at the time of declaration when single quotes are used, use this syntax:

tmp_dir="/tmp/something"
trap 'rm -rf '${tmp_dir}' && debug '${tmp_dir}' removed' EXIT

trap and Functions

The execution sequence can be a function:

trap do-something EXIT

function do-something() {
  echo "exiting ..."
  return 10 # does not influence the exit value of the calling script
  # exit 12 # will replace the exit value of the calling script
}

The function can be declared after the trap registration in the script - trap will capture the function name and it will attempt to execute it when it is triggered by its signal. If the function can be found then, it will be executed correctly.

If the function is executed upon script exit, its return value, if the function does not call exit, does not influence the exit value of the script. However, if the function invokes exit, then the value exit is invoked with will change the exit value of the script. This logically makes sense, as the trap function code is the last that executes.

Only One Code Sequence (Latests) Executes

If multiple code sequences are declared with trap for the same signal, only the last one is executed. The following example:

trap 'echo A' EXIT
trap 'echo B' EXIT

produces:

B

Behavior on Being Invoked from Sub-Shells

If code is registered with trap to react to EXIT in a sub-shell, or in a function that is invoked in a sub-shell, then the registered code will be executed when the sub-shell, and not the top-level invoking shell, exists.

The following code:

$(trap 'echo "a" 1>&2' EXIT)
echo "b"

will display:

a
b

Note that the output should be sent to stderr in the trap code - if the output is sent to stdout, the output is lost, even if the code executes.

Example

Delete Temporary Directory on Exit

local tmp_dir
local preserve_tmp_dir=false
tmp_dir=$(get-tmp-dir) || exit 1
if ${preserve_tmp_dir}; then
    debug "temporary directory ${tmp_dir} will be preserved on exit"
else
    trap "rm -rf ${tmp_dir} && debug deleted temporary directory ${tmp_dir} || warn failed to delete temporary directory ${tmp_dir}" EXIT && \
    debug "registered temporary directory ${tmp_dir} cleanup procedure"
fi

TODO

Reactive Wait Container

Investigate usefulness in case of a reactive wait container. Also see Docker Concepts - Container Exit.

CMD exec /bin/bash -c "trap : TERM INT; sleep infinity & wait"