bashexecbash-trap

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.


Solution

  • 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.