13

In my Bash scripts, I would like to make sure that the script exits as soon as there is an error. (E.g., to avoid a mistaken rm -f * after a failed cd some_directory.) For this reason, I always use the -e flag for bash.

Now, I would also like to execute some cleanup code in some of my scripts. From this blog post I gathered

#!/bin/bash

cd invalid_directory
echo ':('

function clean_up {
  echo "> clean_up"
  exit 0
}
trap clean_up EXIT

The output I get is

./test.sh: line 3: cd: invalid_directory: No such file or directory
:(
> clean_up

so it does what's advertised. However, when using -e for bash, I'm only getting

./test.sh: line 3: cd: invalid_directory: No such file or directory

so the script exits without calling clean_up.

How can I have a bash script exit at all errors and call a clean up script every time?

Nico Schlömer
  • 53,797
  • 27
  • 201
  • 249

1 Answers1

28

You are never reaching the trap command; your shell exits before the trap is configured.

set -e
clean_up () {
    ARG=$?
    echo "> clean_up"
    exit $ARG
} 
trap clean_up EXIT
cd invalid_directory
echo "Shouldn't reach this"

However, it's better to do your own error handling. You often want to vary your behavior depending on the exact reason why your script is exiting, something that is more complicated to do if you are running a single handler for all exits (even if you restrict your trap to ERR instead of EXIT).

cd invalid_directory || { echo "cd to invalid_directory failed" >&2; exit 1; }
echo "Shouldn't reach this"

This doesn't mean you have to abandon your clean_up function. It will still be executed for explicit exits, but it should be restricted to code that should run no matter why your script exits. You can also put a trap on ERR to execute code that should only be executed if you script is exiting with a non-zero exit status.

Nico Schlömer
  • 53,797
  • 27
  • 201
  • 249
chepner
  • 497,756
  • 71
  • 530
  • 681
  • Looks like you (the OP) intended `cd invalid_directory || exit`. Just checking -- otherwise, you will reach *"Shouldn't reach this"* every time. – David C. Rankin Mar 31 '16 at 13:55
  • That's what I would do for real (see my comment on the question). I'm just pointing out why the exit handler isn't executing. I'll update with that approach though. – chepner Mar 31 '16 at 13:57
  • 1
    That what I thought, but I was just scratching my head looking at the *"Shouldn't reach this"* line. – David C. Rankin Mar 31 '16 at 13:57
  • Oh, right; I left it implied that the code was being run with `set -e` :) – chepner Mar 31 '16 at 13:59
  • Thanks for the great answer @chepner! Do you know if it's possible to set the exit code to 0 on `EXIT` and 1 on `ERR`? – Nico Schlömer Mar 31 '16 at 14:07
  • Use separate traps. `trap normal_exit EXIT; trap err_exit ERR`. In either trap, though, you may want to save the value of `$?` at the beginning of your trap in case you need it after any subsequent commands change its value. – chepner Mar 31 '16 at 14:12
  • @chepner Thanks for the tip! Two separate traps really aren't necessary since the `EXIT` trap gets called eventually no matter what. If you'd change your example to `exit $ARG` with `ARG=$?` as the first line of `clean_up`, that'd cover my use case exactly. – Nico Schlömer Mar 31 '16 at 18:38
  • Ah, I mistakenly assumed that the `EXIT` handler would be skipped if the `ERR` handler were triggered. – chepner Mar 31 '16 at 18:47