14

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)?

5

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.

  • eval is always a fan-favorite. – mikeserv Jan 11 '15 at 7:56
23

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
  • 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 Jan 11 '15 at 2:34
  • @math4tots Try the update. – muru Jan 11 '15 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 Jan 11 '15 at 6:35
  • @math4tots You can always do exit ${last_error_code:=0}. – muru Jan 11 '15 at 11:17
  • @avip whatever for? It's already in single quotes, so the variable is only evaluated when the trap is called. – muru Jul 26 '17 at 12:25
8

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.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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