I would like to trap the exit hook of a function. Bash provides a nice signal called RETURN
(non-POSIX) which get called when a function returns, such as
function test_trap() {
trap 'echo trapped' RETURN
sleep 10 # simulate time-consuming commands
echo done
}
I see "trapped" when the function returns, even if I send a SIGINT
with Ctrl
C
. Is there similar functionality for Zsh? I tried EXIT
in Zsh, but it only traps normal returns, not when I interrupt with SIGINT
. I also tried trapping both EXIT
and INT
with the same hook function, but it has two issues:
- The hook expression is evaluated twice when I interrupt it. No big deal; my hook expression happens to be idempotent.
- The
EXIT
in Zsh is local: when returning from the function, the original hook is recovered. This is similar to how a local variable shadows a global one. Unfortunately, theINT
hook is always global, so any hook I specify will overwrite the global one. I would need to remember the originalINT
hook, and recover it later. It’s very tricky to do it correctly; I may have a try on this.
1 Answer 1
That's like for the EXIT trap outside of functions. Exiting and being killed are two different things.
So like outside of functions, you need to handle them separately:
test_trap() {
set -o localoptions -o localtraps
trap 'echo "I am exiting."' EXIT
trap 'echo "I have been interrupted, so"; return 1' INT
echo start
sleep 10
echo end
}
test_trap
echo "returned with: $?"
Which gives:
$ zsh ./that-script
start
^CI have been interrupted, so
I am exiting.
returned with: 1
If you want the shell (not just the function) to exit when it handles the SIGINT signal when received while executing the function, replace return 1
with exit
(or kill yourself with SIGINT after having restored the default handler (trap - INT; kill -s INT "$$"
) if you want to report your death by signal to your parent).
-
It’s sad to hear that I still need to trap the
INT
, because I actually don’t care how the function is terminated. I’m going to simply document the assumption thatINT
trap was unset before calling the function; this assumption makes my clean-up part way easier (otherwise I would need to go this far). Thanks anyway!Franklin Yu– Franklin Yu2020年10月19日 06:07:31 +00:00Commented Oct 19, 2020 at 6:07 -
1Sorry, the first time I read this, I somehow missed the
localtraps
line. Reading it again, there is nothing to "clean up". This is perfect solution! Thank you. One nitpick: I prefer to preserve the original return value for INT, which is 130.Franklin Yu– Franklin Yu2020年11月07日 05:09:07 +00:00Commented Nov 7, 2020 at 5:09 -
@FranklinYu, 130 is the number most shells (including zsh) put in
$?
when the child process they're waiting for dies of a SIGINT, but doingexit 130
is not the same thing as being killed by SIGINT. See Default exit code when process is terminated? for details. And see How to trigger error using Trap command and unix.stackexchange.com/a/230568 for why it may be preferable to kill yourself with SIGINT (like I hint in the answer) than to doexit 130
if you want to report to your parent that you've been interrupted.Stéphane Chazelas– Stéphane Chazelas2020年11月07日 06:45:07 +00:00Commented Nov 7, 2020 at 6:45 -
Thanks for the complete guideline. Does the "
kill
better thanexit
" rule also applies to functions? In other words, is there "kill
better thanreturn
"?Franklin Yu– Franklin Yu2020年11月07日 08:11:32 +00:00Commented Nov 7, 2020 at 8:11 -
@FranklinYu, functions bodies are evaluated by the same shell process. So unless the function bodies is a subshell (as in
func() (echo in a subshell)
, If you kill the process that runs the function, you kill the script. That kill vs exit only matters when the waiting process behaves like bash (zsh doesn't). Inside your own script, you can do what you want based on the exit status of your function.Stéphane Chazelas– Stéphane Chazelas2020年11月07日 09:45:23 +00:00Commented Nov 7, 2020 at 9:45