I'd like to use a bash EXIT
trap and use exec
to avoid spawning a new process. Is this possible?
That is,
#!/bin/bash
touch $0.$$
trap "rm -v $0.$$" EXIT
/bin/echo Hello
removes the temporary file $0.$$
using bash's EXIT
trap while
#!/bin/bash
touch $0.$$
trap "rm -v $0.$$" EXIT
exec /bin/echo Hello
never "fires" the trap (no message from rm
, file $0.$$
exists after completion).
It, of course, makes sense that the trap can't fire as bash is no longer in control after the exec
. Is there some way to make it work and use exec
? This is admittedly out of curiosity moreso than practical matters.
The secret sauce is a pseudo-signal provided by bash, called EXIT, that you can trap; commands or functions trapped on it will execute when the script exits for any reason.
If you are executing a Bash script in your terminal and need to stop it before it exits on its own, you can use the Ctrl + C combination on your keyboard. A ^C character will appear in your terminal to indicate a keyboard interrupt.
The command exec replaces the current shell by the new process defined by the arguments. This also effectively terminates the script after the process terminates, the exit code being the exit code of the new process.
Generally, no. This is not possible for the reason you mention.
That's a boring answer though. Let's look at our options for workarounds:
If we care more about exec semantics and less about starting multiple processes, we can for arbitrary executables do:
{ while kill -0 $$; do sleep 5; done; rm "$0.$$"; } &
exec ./file
which will exec
the file and have another process polling it and doing cleanup when it's done.
If we want to avoid forks and what we're executing is another shell script, we can do
exec bash --rcfile <(echo 'trap "..." exit') -i ./file
to exec
the file and do the cleanup afterwards (as long as the script doesn't exec
or override the trap), without starting a new process. source
ing instead of exec
ing will have much the same effect:
trap "..." exit
source ./file
If we want to get really hacky, we can use LD_PRELOAD
to override exit(3)
and run a command of our choice:
#include <stdlib.h>
void exit(int c) {
char* cmd = getenv("EXIT");
char *argv[] = { "bash", "-c", cmd, NULL };
char *envp[] = { NULL };
execvpe("bash", argv, envp);
}
We can compile this as a library:
$ gcc -shared -fPIC foo.c -o libfoo.so
and then preload it into arbitrary, dynamically linked executables:
$ LD_PRELOAD=./libfoo.so EXIT='echo "This is a hack"' ls *foo*
foo.c libfoo.so
This is a hack
These hacks are fun, but rarely necessary in the real world. The simpler, better and more canonical solution is just to not exec
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With