Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I achieve bash EXIT trap when exec-ing another binary?

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.

like image 903
Rhys Ulerich Avatar asked Jun 08 '14 23:06

Rhys Ulerich


People also ask

What is trap exit in bash?

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.

How do I exit bash script without exiting shell?

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.

Does Exec exit a process?

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.


1 Answers

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. sourceing instead of execing 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.

like image 151
that other guy Avatar answered Oct 31 '22 15:10

that other guy