20

So I have a little script for running some tests.

javac *.java && java -ea Test
rm -f *.class

Now the problem with this is that when I run the script ./test, it will return a success exit code even if the test fails because rm -f *.class succeeds.

The only way I could think of getting it to do what I want feels ugly to me:

javac *.java && java -ea Test
test_exit_code=$?
rm -f *.class
if [ "$test_exit_code" != 0 ] ; then false; fi

But this seems like something of a common problem -- perform a task, clean up, then return the exit code of the original task.

What is the most idiomatic way of doing this (in bash or just shells in general)?

4 Answers 4

26

I'd go with:

javac *.java && java -ea Test
test_exit_code=$?
rm -f *.class
exit "$test_exit_code"

Why jump around when exit is available?


You could use a trap:

trap 'last_error_code=$?' ERR

For example:

$ trap 'last_error_code=$?' ERR
$ false
$ echo $?
1
$ echo $last_error_code $?
1 0
7
  • Ah I agree it's better than my original. But it still feels unsatisfactory that I have to explicitly store the exit code to a variable. Is there no way to 'push' an exit code and 'pop' it again later?
    – math4tots
    Commented Jan 11, 2015 at 2:34
  • @math4tots Try the update.
    – muru
    Commented Jan 11, 2015 at 2:44
  • So with your update I would have to initialize last_error_code to zero and then return at the end so I would have non-zero exit code if any command threw an error? It is a cool trick, but for my two line hack script, I think I prefer @mikeserv 's answer.
    – math4tots
    Commented Jan 11, 2015 at 6:35
  • @math4tots You can always do exit ${last_error_code:=0}.
    – muru
    Commented Jan 11, 2015 at 11:17
  • @avip whatever for? It's already in single quotes, so the variable is only evaluated when the trap is called.
    – muru
    Commented Jul 26, 2017 at 12:25
11

As far as I know the closest thing bash has to a try...finally block from a more C-like programming language (which is what you probably would want if it were available) is the trap construction, which works like this:

trap "rm -f *.class" EXIT
javac *.java && java -ea Test

This will execute "rm -f *.class" when your script exits. If you have something more complex to do, you could put it in a function:

cleanup() {
    ...
}
trap cleanup EXIT
javac *.java && java -ea Test

If you are so inclined, you can turn this into a fairly general idiom that works roughly like a try...catch...finally block in C. Something like this:

(
  trap "catch_block; exit" ERR
  trap finally_block EXIT
  # contents of try goes here
)

Note that the parentheses delimit a subshell; with this construction, only the subshell exits if a command fails, not the whole script. Remember that subshells are somewhat computationally expensive so don't use too many (hundreds) of them. Depending on your script you may be able to achieve the same effect more efficiently with shell functions and trap ... RETURN, but that's up to you to investigate.

0
6

You can wrap the exit and rm commands up into a single simple-command with eval like:

java ... && java ...
eval "rm -f *.class; exit $?"

That way $?'s value when passed to exit is whatever it gets assigned immediately before eval runs.

1
  • 1
    eval is always a fan-favorite.
    – mikeserv
    Commented Jan 11, 2015 at 7:56
0

To make your approach very slight less hairy I'd use exit in a subshell:

javac *.java && java -ea Test
test_exit_code=$?
rm -f *.class
(exit $test_exit_code)

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .