Discussion:
How to trap EXIT like in bash
Thorsten Kampe
2015-04-04 15:20:04 UTC
Permalink
Hi,

in Bash `trap "echo trapped" EXIT` will trigger when the script
terminates normally and on SIGINT, SIGTERM, and SIGHUP.

In Zsh, `trap "echo trapped" EXIT` triggers only on normal exit, but
`trap "echo trapped" EXIT INT` will actually trigger twice on Ctrl-C.

How can I trap normal exit, Ctrl-C, SIGTERM and SIGHUP so trap
function will only run once?

Thorsten
Philippe Troin
2015-04-04 17:08:34 UTC
Permalink
Post by Thorsten Kampe
in Bash `trap "echo trapped" EXIT` will trigger when the script
terminates normally and on SIGINT, SIGTERM, and SIGHUP.
In Zsh, `trap "echo trapped" EXIT` triggers only on normal exit, but
`trap "echo trapped" EXIT INT` will actually trigger twice on Ctrl-C.
How can I trap normal exit, Ctrl-C, SIGTERM and SIGHUP so trap
function will only run once?
I use this:

trap "echo trapped; exit 0" EXIT INT

That does the trick. Both bash and zsh print trapped only once.

I have no idea why zsh is incompatible with bash there, but this
behavior has been in zsh for quite a while.

Phil.
Thorsten Kampe
2015-04-04 17:43:22 UTC
Permalink
* Philippe Troin (Sat, 04 Apr 2015 10:08:34 -0700)
Post by Philippe Troin
Post by Thorsten Kampe
in Bash `trap "echo trapped" EXIT` will trigger when the script
terminates normally and on SIGINT, SIGTERM, and SIGHUP.
In Zsh, `trap "echo trapped" EXIT` triggers only on normal exit, but
`trap "echo trapped" EXIT INT` will actually trigger twice on Ctrl-C.
How can I trap normal exit, Ctrl-C, SIGTERM and SIGHUP so trap
function will only run once?
trap "echo trapped; exit 0" EXIT INT
I just tested it: Zsh is trapped once but bash twice on INT.

This works:
```
if [[ $shell = bash ]]
then
trap "echo trapped" EXIT

elif [[ $shell = zsh ]]
then
trap "echo trapped; exit" INT
fi
```
BUT: it does not work when I extend the signals to
```
elif [[ $shell = zsh ]]
then
trap "echo trapped; exit" INT HUP TERM
fi
```

Then Zsh does actually ignore the kill (TERM) signal.

Thorsten
Philippe Troin
2015-04-04 18:20:26 UTC
Permalink
Post by Thorsten Kampe
* Philippe Troin (Sat, 04 Apr 2015 10:08:34 -0700)
Post by Philippe Troin
Post by Thorsten Kampe
in Bash `trap "echo trapped" EXIT` will trigger when the script
terminates normally and on SIGINT, SIGTERM, and SIGHUP.
In Zsh, `trap "echo trapped" EXIT` triggers only on normal exit, but
`trap "echo trapped" EXIT INT` will actually trigger twice on Ctrl-C.
How can I trap normal exit, Ctrl-C, SIGTERM and SIGHUP so trap
function will only run once?
trap "echo trapped; exit 0" EXIT INT
I just tested it: Zsh is trapped once but bash twice on INT.
```
if [[ $shell = bash ]]
then
trap "echo trapped" EXIT
elif [[ $shell = zsh ]]
then
trap "echo trapped; exit" INT
fi
```
Yes, you're right. For some reason it did work for me but I can't
reproduce it. This would work everywhere:

trap "echo trapped; trap - EXIT; exit 0" EXIT INT

With bash:

% bash --version
GNU bash, version 4.2.53(1)-release (x86_64-redhat-linux-gnu)
Copyright (C) 2011 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>

This is free software; you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
% for i in INT TERM HUP QUIT; do bash -c 'trap "echo trapped \$?; trap - EXIT; exit 0" EXIT INT TERM HUP QUIT; sleep 5' & sleep 1; kill -$i -$!; wait; done
[2] 28903
trapped 130
[2] + done bash -c
[1] 28906
Terminated
trapped 143
[1] + done bash -c
[1] 28909
Hangup
trapped 129
[1] + done bash -c
[1] 28912
Quit
trapped 131
[1] + done bash -c
%

With zsh:

% zsh --version
zsh 5.0.7 (x86_64-redhat-linux-gnu)
% for i in INT TERM HUP QUIT; do zsh -fc 'trap "echo trapped \$?; trap - EXIT; exit 0" EXIT INT TERM HUP QUIT; sleep 5' & sleep 1; kill -$i -$!; wait; done
[2] 28936
trapped 130
[2] + done zsh -fc
[1] 28940
trapped 143
[1] + done zsh -fc
[1] 28943
trapped 129
[1] + done zsh -fc
[1] 28947
trapped 131
[1] + done zsh -fc
%
Post by Thorsten Kampe
BUT: it does not work when I extend the signals to
```
elif [[ $shell = zsh ]]
then
trap "echo trapped; exit" INT HUP TERM
fi
```
Then Zsh does actually ignore the kill (TERM) signal.
That's not the behavior I'm seeing above.

Phil.
Bart Schaefer
2015-04-04 18:48:59 UTC
Permalink
On Apr 4, 7:43pm, Thorsten Kampe wrote:
}
} elif [[ $shell = zsh ]]
} then
} trap "echo trapped; exit" INT HUP TERM
} fi
}
} Then Zsh does actually ignore the kill (TERM) signal.

It doesn't ignore the signal, but it may not run the trap either. You
need the trap on EXIT in there:

trap "echo trapped; exit" EXIT INT HUP TERM

That's because it matters whether the shell itself gets the signal, or
an external job that's been run *by* the shell gets the signal.

E.g. if I run a script that executes "sleep" and kill the sleep, the
TERM trap is not run but the EXIT trap will be. If I kill the shell
itself, the TERM trap is run but the EXIT trap is not.
Thorsten Kampe
2015-04-04 19:59:15 UTC
Permalink
* Bart Schaefer (Sat, 4 Apr 2015 11:48:59 -0700)
Post by Bart Schaefer
}
} elif [[ $shell = zsh ]]
} then
} trap "echo trapped; exit" INT HUP TERM
} fi
}
} Then Zsh does actually ignore the kill (TERM) signal.
It doesn't ignore the signal, but it may not run the trap either. You
trap "echo trapped; exit" EXIT INT HUP TERM
That's because it matters whether the shell itself gets the signal, or
an external job that's been run *by* the shell gets the signal.
E.g. if I run a script that executes "sleep" and kill the sleep, the
TERM trap is not run but the EXIT trap will be. If I kill the shell
itself, the TERM trap is run but the EXIT trap is not.
This works if I kill the sleep command. It does not work if I kill
the shell that runs the script.

Thorsten
Bart Schaefer
2015-04-04 20:25:28 UTC
Permalink
On Apr 4, 9:41pm, Thorsten Kampe wrote:
}
} > zshexit() { echo trapped }
} > trap exit HUP INT TERM
}
} As stated to Philippe: this works in regard to exit and INT, but not
} for HUP and TERM. The script ignores any `kill PID` request.
}
} Tested on Cygwin and Ubuntu 14.10.

Oh. You need "setopt TRAPS_ASYNC":

While waiting for a program to exit, handle signals and run traps
immediately. Otherwise the trap is run after a child process has
exited. Note this does not affect the point at which traps are
run for any case other than when the shell is waiting for a child
process.

The signal isn't ignored, it just isn't handled right away. Normally any
foreground child process is the "group leader" and receives the signals
before the parent shell.

I wonder if this should be on by default in "sh" emulation.
Bart Schaefer
2015-04-04 20:55:57 UTC
Permalink
On Apr 4, 1:25pm, Bart Schaefer wrote:
}
} } Tested on Cygwin and Ubuntu 14.10.
}
} Oh. You need "setopt TRAPS_ASYNC":
}
} The signal isn't ignored, it just isn't handled right away.

The original discussion about TRAPS_ASYNC starts here:

http://www.zsh.org/mla/workers//2004/msg00460.html

It seems asserted in that thread that the Bash behavior is not only not
compatible with FreeBSD, but is also not POSIX compliant, which is why
the default for zsh got changed.
Bart Schaefer
2015-04-04 21:25:24 UTC
Permalink
On Apr 4, 1:55pm, Bart Schaefer wrote:
}
} http://www.zsh.org/mla/workers//2004/msg00460.html
}
} It seems asserted in that thread that the Bash behavior is not only not
} compatible with FreeBSD, but is also not POSIX compliant, which is why
} the default for zsh got changed.

Hmm, except that the NEWS file and the early part of that thread claim
that TRAPS_ASYNC is the default for zsh because of historic behavior,
but then workers/19858 changes OPT_EMULATE|OPT_NONBOURNE to 0 so that
TRAPS_ASYNC is never on by default.

Was this just a botch in 19858 (should have been OPT_ZSH ?) that no one
ever noticed? I can't tell from the archived discussion whether this was
done intentionally.
Peter Stephenson
2015-04-10 16:28:33 UTC
Permalink
On Sat, 4 Apr 2015 14:25:24 -0700
Post by Bart Schaefer
}
} http://www.zsh.org/mla/workers//2004/msg00460.html
}
} It seems asserted in that thread that the Bash behavior is not only not
} compatible with FreeBSD, but is also not POSIX compliant, which is why
} the default for zsh got changed.
Hmm, except that the NEWS file and the early part of that thread claim
that TRAPS_ASYNC is the default for zsh because of historic behavior,
but then workers/19858 changes OPT_EMULATE|OPT_NONBOURNE to 0 so that
TRAPS_ASYNC is never on by default.
Was this just a botch in 19858 (should have been OPT_ZSH ?) that no one
ever noticed? I can't tell from the archived discussion whether this was
done intentionally.
Would guess so, given the discussion, and I don't suppose we're going to
find out after this length of time, so maybe this should be rectified.

pws
Bart Schaefer
2015-04-10 21:16:15 UTC
Permalink
On Apr 10, 5:28pm, Peter Stephenson wrote:
} Subject: Re: How to trap EXIT like in bash
}
} > }
} > } http://www.zsh.org/mla/workers//2004/msg00460.html
} > }
} > } It seems asserted in that thread that the Bash behavior is not only not
} > } compatible with FreeBSD, but is also not POSIX compliant, which is why
} > } the default for zsh got changed.
} >
} > Hmm, except that the NEWS file and the early part of that thread claim
} > that TRAPS_ASYNC is the default for zsh because of historic behavior,
} >
} > Was this just a botch in 19858 (should have been OPT_ZSH ?) that no one
} > ever noticed?
}
} Would guess so, given the discussion, and I don't suppose we're going to
} find out after this length of time, so maybe this should be rectified.

The Yodl doc was changed to match the code, though, so perhaps given that
it has been 10+ years, the thing to do is to put a correction in the NEWS
file and leave the option defaults alone.

Clearly it doesn't make that much difference in real life, except for the
rare cases where it's critical.

Thorsten Kampe
2015-04-04 21:06:50 UTC
Permalink
* Bart Schaefer (Sat, 4 Apr 2015 13:25:28 -0700)
Post by Bart Schaefer
}
} > zshexit() { echo trapped }
} > trap exit HUP INT TERM
}
} As stated to Philippe: this works in regard to exit and INT, but not
} for HUP and TERM. The script ignores any `kill PID` request.
}
} Tested on Cygwin and Ubuntu 14.10.
Confirming that setting this options makes a Zsh script terminate and
execute the specified traps.

Thorsten
Thorsten Kampe
2015-04-04 20:45:30 UTC
Permalink
* Bart Schaefer (Sat, 4 Apr 2015 11:48:59 -0700)
Post by Bart Schaefer
}
} elif [[ $shell = zsh ]]
} then
} trap "echo trapped; exit" INT HUP TERM
} fi
}
} Then Zsh does actually ignore the kill (TERM) signal.
It doesn't ignore the signal, but it may not run the trap either. You
trap "echo trapped; exit" EXIT INT HUP TERM
That's because it matters whether the shell itself gets the signal, or
an external job that's been run *by* the shell gets the signal.
E.g. if I run a script that executes "sleep" and kill the sleep, the
TERM trap is not run but the EXIT trap will be. If I kill the shell
itself, the TERM trap is run but the EXIT trap is not.
It gets even weirder: neither `kill -HUP`, nor `kill -INT` nor `kill
-TERM` with the PID of the shell have any influence on the script if
these signals are trapped. What does have an influence is a Ctrl-C
and `kill -KILL`.

The kill commands (except `kill -INT`) work immediately with bash
running the script.

Thorsten
Bart Schaefer
2015-04-04 20:52:20 UTC
Permalink
On Apr 4, 10:45pm, Thorsten Kampe wrote:
}
} It gets even weirder: neither `kill -HUP`, nor `kill -INT` nor `kill
} -TERM` with the PID of the shell have any influence on the script if
} these signals are trapped. What does have an influence is a Ctrl-C
} and `kill -KILL`.

This is still NO_TRAPS_ASYNC. Ctrl-C from the terminal is sent to the
whole process group, so both zsh and the child get the signal and the
trap is run effectively immediately (because the child has exited).

KILL isn't trappable, so zsh can't delay handling that.
Bart Schaefer
2015-04-04 18:35:57 UTC
Permalink
On Apr 4, 5:20pm, Thorsten Kampe wrote:
}
} In Zsh, `trap "echo trapped" EXIT` triggers only on normal exit, but
} `trap "echo trapped" EXIT INT` will actually trigger twice on Ctrl-C.

Hmm. This seems to be a side-effect of the rule that the EXIT trap does
not run from inside other traps. The default response to INT in a script
is to behave as if 'trap "exit 130" INT' so the EXIT trap is not run. In
your second (executes twice) example, the script doesn't exit until after
the INT trap has completed.

} How can I trap normal exit, Ctrl-C, SIGTERM and SIGHUP so trap
} function will only run once?

Just add an explicit "exit" to the trap itself:

trap "echo trapped; exit" EXIT HUP INT TERM

This also works:

zshexit() { echo trapped }
trap exit HUP INT TERM

Curiously in an interactive shell, the following prints "trapped" exactly
one time, even though my first answer above also works interactively:

trap "echo trapped" EXIT
trap exit HUP INT TERM

However, that does not work in a script. I'm not sure why interactive
matters here.
Thorsten Kampe
2015-04-04 19:41:31 UTC
Permalink
* Bart Schaefer (Sat, 4 Apr 2015 11:35:57 -0700)
Post by Bart Schaefer
}
} In Zsh, `trap "echo trapped" EXIT` triggers only on normal exit, but
} `trap "echo trapped" EXIT INT` will actually trigger twice on Ctrl-C.
Hmm. This seems to be a side-effect of the rule that the EXIT trap does
not run from inside other traps. The default response to INT in a script
is to behave as if 'trap "exit 130" INT' so the EXIT trap is not run. In
your second (executes twice) example, the script doesn't exit until after
the INT trap has completed.
} How can I trap normal exit, Ctrl-C, SIGTERM and SIGHUP so trap
} function will only run once?
trap "echo trapped; exit" EXIT HUP INT TERM
zshexit() { echo trapped }
trap exit HUP INT TERM
As stated to Philippe: this works in regard to exit and INT, but not
for HUP and TERM. The script ignores any `kill PID` request.

Tested on Cygwin and Ubuntu 14.10.

Thorsten
Han Pingtian
2015-04-07 07:44:13 UTC
Permalink
Post by Bart Schaefer
Hmm. This seems to be a side-effect of the rule that the EXIT trap does
not run from inside other traps. The default response to INT in a script
is to behave as if 'trap "exit 130" INT' so the EXIT trap is not run. In
Curiously in an interactive shell, the following prints "trapped" exactly
trap "echo trapped" EXIT
trap exit HUP INT TERM
However, that does not work in a script. I'm not sure why interactive
matters here.
I think because the rule that EXIT trap doesn't run from inside other
traps, the phenomenon of script not working is correct and the
interactive shell working is wrong.

Am I right?
Loading...