61

Many examples for trap use trap ... INT TERM EXIT for cleanup tasks. But is it really necessary to list all the three sigspecs?

The manual says:

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 or SIGTERM. 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 sole EXIT would miss?

  • 3
    Also keep in mind that with a spec like INT TERM EXIT the cleanup code is executed twice when SIGTERM or SIGINT is received. – maxschlepzig Oct 29 '16 at 16:49
19

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
# 
  • 3
    dash also doesn't trap on just EXIT when it receives SIGINT/SIGTERM. – maxschlepzig Oct 29 '16 at 16:50
  • 3
    zsh as well - thus, perhaps bash is the only shell where EXIT also does match signals. – maxschlepzig Oct 29 '16 at 16:59
  • @maxschlepzig zsh doesn't trap on EXIT when it receives INT, but it does when it receives TERM. EDIT: I just noticed how old this was... – JoL May 15 '18 at 15:57
25

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
  • 1
    The solution with cleanup does the right thing - very elegant! It has become an idiom for my bash scripts with mktemp calls. – Bjoern Dahlgren Jan 6 '16 at 13:20
  • 2
    Is that exit necessary in cleanup? – jarno Sep 9 '16 at 6:02
  • 2
    This doesn't work if you have shellscript errors in your code that cause it to exit prematurely. – ijw Nov 10 '16 at 3:30
  • 2
    @ijw: In Bash and Ksh, you can trap ERR to handle that, but it is not portable. – Zaz Feb 6 '17 at 14:40
  • 3
    This 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 call cleanup at the end of the script. – Nicholas Pipitone Jul 20 '18 at 20:47
10

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.

  • 2
    The caller would just get 1 as the exit code, no matter what signal caused the exit, while withour trap the caller would get 130 for SIGINT, 143 for SIGTERM, etc. So I would capture and pass the correct exit code as: sig_cleanup() { err=$?; trap '' EXIT; (exit $err); cleanup; }. – musiphil Nov 14 '16 at 5:12
  • 2
    Can 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 the EXIT redundant? – Six Feb 3 '17 at 8:33

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.