"trap ... INT TERM EXIT" really necessary?
Solution 1
The POSIX spec doesn't say much about the conditions resulting in executing the EXIT trap, only about what its environment must look like when it is executed.
In Busybox's ash shell, your trap-exit test does not echo 'TRAP' before exiting due to either SIGINT or SIGTERM. I would suspect there are other shells in existance that may not work that way as well.
# /tmp/test.sh & sleep 1; kill -INT %1
#
[1]+ Interrupt /tmp/test.sh
#
#
# /tmp/test.sh & sleep 1; kill -TERM %1
#
[1]+ Terminated /tmp/test.sh
#
Solution 2
Yes, there is a difference.
This script will exit when you press Enter, or send it SIGINT
or SIGTERM
:
trap '' EXIT
echo ' --- press ENTER to close --- '
read response
This script will exit when you press Enter:
trap '' EXIT INT TERM
echo ' --- press ENTER to close --- '
read response
* Tested in sh, Bash, and Zsh. (no longer works in sh when you add a command for trap to run)
There's also what @Shawn said: Ash and Dash don't trap signals with EXIT
.
So, to handle signals robustly, it's best to avoid trapping EXIT
altogether, and use something like this:
cleanup() {
echo "Cleaning stuff up..."
exit
}
trap cleanup INT TERM
echo ' --- press ENTER to close --- '
read var
cleanup
Solution 3
Refining the last answer, because it has issues:
# Our general exit handler
cleanup() {
err=$?
echo "Cleaning stuff up..."
trap '' EXIT INT TERM
exit $err
}
sig_cleanup() {
trap '' EXIT # some shells will call EXIT after the INT handler
false # sets $?
cleanup
}
trap cleanup EXIT
trap sig_cleanup INT QUIT TERM
Points above:
INT and TERM handlers don't quit for me when I test - they handle the error then the shell returns to exiting (and this is not too surprising). So I ensure that the cleanup exits afterwards, and in the case of the signals always uses an error code (and in the other case of a normal exit, preserves the error code).
With bash, it seems that exiting in the INT handler also calls the EXIT handler, hence I untrap the exit handler and call it myself (which will work in any shell regardless of behaviour).
I trap exit because shell scripts can exit before they reach the bottom - syntax errors, set -e and a nonzero return, simply calling exit. You can't rely on a shellscript getting to the bottom.
SIGQUIT is Ctrl-\ if you've never tried it. Gets you a bonus coredump. So I think it's also worth trapping, even if it's a little obscure.
Past experience says if you (like me) always press Ctrl-C several times, you'll sometimes catch it half way through the cleanup part of your shell script, so this works but not always as perfectly as you'd like.
Solution 4
This is how you can make the Bash script report its return code $?
, while being able to catch the SIGINT and SIGTERM signals. I find this very useful for scripts running in a CI/CD pipeline:
notify() {
[[ $1 = 0 ]] || echo ❌ EXIT $1
# you can notify some external services here,
# ie. Slack webhook, Github commit/PR etc.
}
trap '(exit 130)' INT
trap '(exit 143)' TERM
trap 'rc=$?; notify $rc; exit $rc' EXIT
Related videos on Youtube
musiphil
Updated on September 18, 2022Comments
-
musiphil over 1 year
Many examples for
trap
usetrap ... INT TERM EXIT
for cleanup tasks. But is it really necessary to list all the three sigspecs?If a SIGNAL_SPEC is EXIT (0) ARG is executed on exit from the shell.
which I believe applies whether the script finished normally or it finished because it received
SIGINT
orSIGTERM
. An experiment also confirms my belief:$ cat ./trap-exit #!/bin/bash trap 'echo TRAP' EXIT sleep 3 $ ./trap-exit & sleep 1; kill -INT %1 [1] 759 TRAP [1]+ Interrupt ./trap-exit $ ./trap-exit & sleep 1; kill -TERM %1 [1] 773 TRAP [1]+ Terminated ./trap-exit
Then why do so many examples list all of
INT TERM EXIT
? Or did I miss something and is there any case where a soleEXIT
would miss?-
Admin over 7 yearsAlso keep in mind that with a spec like
INT TERM EXIT
the cleanup code is executed twice whenSIGTERM
orSIGINT
is received.
-
-
Bjoern Dahlgren over 8 yearsThe solution with cleanup does the right thing - very elegant! It has become an idiom for my bash scripts with
mktemp
calls. -
maxschlepzig over 7 years
dash
also doesn't trap on justEXIT
when it receivesSIGINT/SIGTERM
. -
maxschlepzig over 7 years
zsh
as well - thus, perhapsbash
is the only shell whereEXIT
also does match signals. -
ijw over 7 yearsThis doesn't work if you have shellscript errors in your code that cause it to exit prematurely.
-
Chris Wolfe over 7 yearsCan you clarify the purpose of
trap '' EXIT INT TERM
in the cleanup function? Is this to prevent accidental user interruption of cleanup that you mentioned in the last paragraph? Isn't theEXIT
redundant? -
Zaz over 7 years@ijw: In Bash and Ksh, you can trap
ERR
to handle that, but it is not portable. -
JoL about 6 years@maxschlepzig
zsh
doesn't trap onEXIT
when it receivesINT
, but it does when it receivesTERM
. EDIT: I just noticed how old this was... -
Nicholas Pipitone almost 6 yearsThis solution isn't robust when another shell calls it. It doesn't handle wait on cooperative exit; you will want to
trap - INT TERM; kill -2 $$
as the last line of cleanup, to tell the parent shell that it exited prematurely. If a parent shell foobar.sh calls your script (foo.sh), and then calls bar.sh, you don't want bar.sh to execute if INT/TERM is sent to your foo.sh.trap cleanup EXIT
will handle this propagation automatically, so it is IMO the most robust. It also means you wouldn't have to callcleanup
at the end of the script. -
Nicholas Pipitone almost 6 yearsDoing
kill -2 $$
will propagate up the chain of non-interactive shells, killing each parent shell, and then stop at the first interactive shell, giving stdin back to the keyboard (Which is as excepted, ctrl+c should return to the interactive shell, not hang). Quite sad ash doesn't work withEXIT
as bash makes it real easy (so nvm on EXIT being robust if you're talking compatibility), but the kill is important there. -
Kevin over 4 yearsWith
set -o errexit
, callingfalse
insig_cleanup()
impedescleanup()
from being called. In this case,trap cleanup EXIT
seems sufficient. -
mstorsjo over 4 years@musiphil -
err=$?
insig_cleanup
only gives 0 in dash, so apparently the original signal code is lost when trapping signals there, and that's why something like callingfalse
is required. But as @Enno says, this would need aset +e
insig_cleanup
to avoid exiting beforecleanup
is called. -
jarno about 4 years@NicholasPipitone Mere
trap cleanup EXIT
will not do, if you want to interupt the "ping" loop at mywiki.wooledge.org/… -
jarno about 4 years@mstorsjo
err=$?
seems to work in dash 0.5.10.2-6; by which version did you try? -
jarno about 4 years@musiphil why not use
sig_cleanup() { err=$?; trap - EXIT; cleanup; exit $err; }
? -
mstorsjo about 4 years@jarno - I tried dash 0.5.8-2.10 (ubuntu 18.04), and I can confirm that I see that it behaves the way you describe in dash 0.5.10.2. I bisected it down to this change, git.kernel.org/pub/scm/utils/dash/dash.git/commit/…, which was included in dash 0.5.9.
-
jarno about 4 years@mstorsjo if the changed code is already in 0.5.8, how could the change be in 0.5.9? BTW In my experience mere
exit
works in dash, too, but it does not work in bash. -
mstorsjo about 4 years@jarno - No, the change is in 0.5.9. With 0.5.8,
$?
is zero insig_cleanup()
, in 0.5.9 and onwards it has the actual signal return value. -
jarno about 4 years@mstorsjo I tested it in Ubuntu 18.04 and it worked.
$?
is 130 after Ctrl-C. It has dash 0.5.8-2.10. -
mstorsjo about 4 years@jarno With pastebin.com/d74jkFVU on Ubuntu 18.04 I'm getting
$?
insig_cleanup
, if I press ctrl+c during the sleep. With a newer dash I'm getting 130 there. -
jarno about 4 years@mstorsjo oh, but that is only, if you have errexit flag set. I do not see why it should affect, though. And in never version it is fixed.
-
jarno about 4 years@mstorsjo see also my related answer.
-
Noam Manos almost 3 yearsRedirecting INT and TERM to the EXIT trap is an elegant solution, which prevents dual calls to the trap command. Thanks!