I wrote the following atexit implementation for Bash

#! /bin/bash

set -e

ATEXIT=()

function atexit_handler
{
  local EXPR
  for EXPR in "${ATEXIT[@]}"; do
    echo "evaluating $EXPR"
    eval "$EXPR" || true
  done
}

trap atexit_handler EXIT

function atexit ()
{
  local EXPR
  for EXPR in "$@"; do
    ATEXIT+=("$EXPR")
  done
}

atexit true
atexit false
atexit "echo bye"

false

I am wondering, if this is possible without the use of eval.

I don't think it's possible without the eval. A close candidate might be:

atexit_handler() {
  local EXPR
  for EXPR in "${ATEXIT[@]}"; do
    echo "evaluating $EXPR"
    $EXPR || true
  done
}

But this won't work with non-trivial expressions like this:

atexit 'for i in "a b" c; do echo $i; done'

Using the function keyword in function declaration like this is an outdated practice:

function atexit_handler
{
  local EXPR
  for EXPR in "${ATEXIT[@]}"; do
    echo "evaluating $EXPR"
    eval "$EXPR" || true
  done
}

Use the more modern style I wrote in the previous example above.


Instead of this:

  for EXPR in "$@"; do
    ATEXIT+=("$EXPR")
  done

A simpler way to iterate over $@:

  for EXPR; do
    ATEXIT+=("$EXPR")
  done

But actually, as @etan-reisner pointed out in a comment, it's silly to loop here when you can add the entire arg list in one swift move:

ATEXIT+=("$@")
  • 2
    Why bother iterating over "$@" at all? Why not just splat it into the () directly? ATEXIT+=("$@") – Etan Reisner Oct 23 '14 at 18:45
  • 1
    Using set -e means it works even when the script is run "manually" (i.e. bash myscript.sh) which the shebang does not. – Etan Reisner Oct 23 '14 at 18:46
  • @EtanReisner spot on! Thanks for that. I added the first point to my answer and removed the second. – janos Oct 23 '14 at 20:34
  • Emacs does not color the function names in sh-mode. Using the function keyword adds some syntax highlighting to functions. – ceving Oct 27 '14 at 9:51
  • At the time I posted this, I didn't know what EXIT really meant. Thanks @TobySpeight for pointing this out, dropped that point. (I fear I may have said things like this in other old answers too. I should probably search and verify all :-/) – janos Nov 27 at 21:57

Sorry for excavating this, but just found it and wanted to try.

Will offer what I can.

#! /bin/bash

This is no crime, but it's a tad more portable (if that matters at all to you) if you use

#! /bin/env bash

env itself is virtually always in /bin, though some installations move the interpreter binaries.

I don't personally like or use set -e. It doesn't give me as much control over what happens. Personally, I prefer traps, something like this:

trap 'echo "ERROR $? at $0:$LINENO - [$BASH_COMMAND]"' err

Then I get useful feedback.

$: bogus
bash: bogus: command not found
ERROR 127 at /usr/bin/bash:73 - [bogus]

As for the eval, how about creating a controlled file to source at exit time?

atexit_handler() {
  tmp=$(mktemp)
  printf "%s\n" "${ATEXIT[@]}" >| $tmp
  trap 'echo "Error $? in exit handler when processing [$BASH_COMMAND]"' err
  . $tmp
}

This would allow for more arbitrarily complex possibilities, without really worsening the security risk of the eval, which isn't catastrophic if your code registers all the commands going into the handler anyway. It also leaves you the option of scheduling the cleanup of the file with the handler, so that if it succeeds it leaves no mess, but if it fails will abort and let you debug the file.

atexit true
atexit "echo bye"
atexit 'sing() { for i in do re me;do echo "$i"; done; }'
atexit sing
atexit false
atexit 'rm $tmp' # defer evaluation 

I love this idea, btw. I may start using it. :)

  • I've never seen a /bin/env - on Debian-based systems at least, it's always in /usr/bin. – Toby Speight Nov 27 at 16:48
  • As long as it's working, I wouldn't sweat it. YMMV; even where I work, across a lot of systems, they are mostly all RHEL 6 or 7 and vaguely similar in structure, so it has rarely been an issue. It's just something I have been told to cultivate as a habit, and I have (anecdotally) had fewer issues since I started doing it. shrug :D – Paul Hodges Nov 27 at 16:58
  • So RHEL have /bin/env instead of /usr/bin/env? Or as well as? If it has both, then the latter would seem to be the safer choice. – Toby Speight Nov 27 at 18:21
  • Common to have it linked in both locations just in case. :) – Paul Hodges Nov 27 at 18:31

Still uses eval, but it seems cleaner to do:

atexit() {
        cmd="$1"
        eval set -- $(trap -p EXIT)
        trap "$cmd; $3" EXIT
}

To allow semi-colons (eg, atexit "echo foo;") and aesthetic purity, you might like:

atexit() {
        cmd="${1%;}"
        eval set -- $(trap -p EXIT)
        trap "$cmd${3:+; }$3" EXIT
}
  • You have presented an alternative solution, but haven't reviewed the code. Please edit to show what aspects of the question code prompted you to write this version, and in what ways it's an improvement over the original. It may be worth (re-)reading How to Answer. – Toby Speight Nov 27 at 15:10

Your Answer

By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

Not the answer you're looking for? Browse other questions tagged or ask your own question.