74

Is there a linux bash command like the java try catch finally? Or does the linux shell always go on?

try {
   `executeCommandWhichCanFail`
   mv output
} catch {
    mv log
} finally {
    rm tmp
}
codeforester
  • 28,846
  • 11
  • 78
  • 104
Jetse
  • 1,426
  • 2
  • 15
  • 22

6 Answers6

118

Based on your example, it looks like you are trying to do something akin to always deleting a temporary file, regardless of how a script exits. In Bash to do this try the trap builtin command to trap the EXIT signal.

#!/bin/bash

trap 'rm tmp' EXIT

if executeCommandWhichCanFail; then
    mv output
else
    mv log
    exit 1 #Exit with failure
fi

exit 0 #Exit with success

The rm tmp statement in the trap is always executed when the script exits, so the file "tmp" will always tried to be deleted.

Installed traps can also be reset; a call to trap with only a signal name will reset the signal handler.

trap EXIT

For more details, see the bash manual page: man bash

theycallhimart
  • 1,731
  • 1
  • 12
  • 4
  • 3
    One nice thing about using 'trap' is that you can also catch other signals besides EXIT. In particular, you can catch SIGINT (Control-C). To do so, just tack it on the end of the trap statement. For example `trap 'rm tmp' EXIT SIGINT`. – ishmael Jun 18 '14 at 23:29
  • yep,SIGTERM would also be helpful. make the script robust.[clean up tmp files even if 'been killed'] – C19 Jan 07 '15 at 13:05
  • 7
    From a quick test it seems that the `EXIT` handler is **also** called for `SIGINT` and `SIGTERM` – Cuadue Jan 08 '15 at 21:59
  • 3
    This is for sure the cleanest way to do it. – chesterbr May 29 '15 at 19:11
  • 4
    Good answer; it may be worth pointing out that execution of the trap doesn't affect the exit status of the script - that's an advantage over `if/else` or `||` approaches, and more like C++ or Java `try`/`catch`. – Toby Speight Sep 07 '17 at 14:39
  • 2
    To expand on the above comments: in my testing, Control-C causes SIGINT to fire first, followed by EXIT. Therefore, for the purpose being discussed here, it's redundant to trap SIGINT, and in fact trapping both causes your cleanup command to be executed twice -- very likely not what you want. Trapping SIGTERM on the other hand caused the program to NOT terminate when it received a kill. A kill -9 skips all three traps. So trapping SIGTERM is not recommended in this case either. (If you do decide to trap SIGTERM for some reason, you probably want to call 'exit' in your trap handler.) – benkc Jan 10 '18 at 21:11
105

Well, sort of:

{ # your 'try' block
    executeCommandWhichCanFail &&
    mv output
} || { # your 'catch' block
    mv log
}

 rm tmp # finally: this will always happen
Faiz
  • 15,245
  • 5
  • 45
  • 37
  • 4
    Note you have to use the `&&` after the `executeCommandWhichCanFail` otherwise it proceeds blindly. Even if you precede it with use `set -e` (which I don't understand). – AJP Jan 09 '14 at 14:55
  • 5
    Short and concise. But I'd prefer using `trap`, as `||` doesn't ensure the other part gets executed even under exceptional conditions (signals), which is pretty much what one expects from `finally`. – Petr Sep 23 '15 at 09:00
  • You could probably use `(...)` instead of `{...}` and then you wouldn't have to use && on every line – Alexander Mills Apr 25 '19 at 23:18
1

mv takes two parameters, so may be you really wanted to cat the output file's contents:

echo `{ execCommand && cat output ; } || cat log`
rm -f tmp
1

I found success in my script with this syntax:

# Try, catch, finally
(echo "try this") && (echo "and this") || echo "this is the catch statement!"

# this is the 'finally' statement
echo "finally this"

If either try statement throws an error or ends with exit 1, then the interpreter moves on to the catch statement and then the finally statement.

If both try statements succeed (and/or end with exit), the interpreter will skip the catch statement and then run the finally statement.

Example_1:

goodFunction1(){
  # this function works great
  echo "success1"
}

goodFunction2(){
  # this function works great
  echo "success2"
  exit
}

(goodFunction1) && (goodFunction2) || echo "Oops, that didn't work!"

echo "Now this happens!"

Output_1

success1
success2
Now this happens!

Example _2

functionThrowsErr(){
  # this function returns an error
  ech "halp meh"
}

goodFunction2(){
  # this function works great
  echo "success2"
  exit
}

(functionThrowsErr) && (goodFunction2) || echo "Oops, that didn't work!"

echo "Now this happens!"

Output_2

main.sh: line 3: ech: command not found
Oops, that didn't work!
Now this happens!

Example_3

functionThrowsErr(){
  # this function returns an error
  echo "halp meh"
  exit 1
}

goodFunction2(){
  # this function works great
  echo "success2"
}

(functionThrowsErr) && (goodFunction2) || echo "Oops, that didn't work!"

echo "Now this happens!"

Output_3

halp meh
Oops, that didn't work!
Now this happens!

Note that the order of the functions will affect output. If you need both statements to be tried and caught separately, use two try catch statements.

(functionThrowsErr) || echo "Oops, functionThrowsErr didn't work!"
(goodFunction2) || echo "Oops, good function is bad"

echo "Now this happens!"

Output

halp meh
Oops, functionThrowsErr didn't work!
success2
Now this happens!
DogeCode
  • 107
  • 1
  • 8
  • and what if one of these "try statements" raise error (throw exception) ? how does it handle errors (exceptions) ? – faza Feb 29 '20 at 18:58
  • if one of the try statements exits from returning an error, or because there is a "exit 1" command, the interpreter will move on to the "catch", followed by the finally statement. however, if a try statement exit with an "exit" command or by simply completing successfully, then the interpreter will move on to the finally statement without running the catch statement. – DogeCode Mar 01 '20 at 00:04
  • I updated my answer with examples that show how errors are handled in various scenarios depending what your program flow requires. – DogeCode Mar 01 '20 at 01:05
0

Another way to do it would be:

set -e;  # stop on errors

mkdir -p "$HOME/tmp/whatevs"

exit_code=0

(
  set +e;
  (
    set -e;
    echo 'foo'
    echo 'bar'
    echo 'biz'
  )
  exit_code="$?"
)

rm -rf "$HOME/tmp/whatevs"

if [[ "exit_code" != '0' ]]; then
   echo 'failed';
fi 

although the above doesn't really offer any benefit over:

set -e;  # stop on errors

mkdir -p "$HOME/tmp/whatevs"

exit_code=0

(
    set -e;
    echo 'foo'
    echo 'bar'
    echo 'biz'
    exit 44;
    exit 43;

) || {
   exit_code="$?"  # exit code of last command which is 44
}

rm -rf "$HOME/tmp/whatevs"

if [[ "exit_code" != '0' ]]; then
   echo 'failed';
fi 
0

Warning: exit traps are not always excuted. Since writing this answer I have run into situations, where my exit trap would not be executed, causing loss of files, the reason of which I haven't found yet.

The issue occurred when I stopped a python script with Ctrl+C, which in turn had executed a bash script using exit traps -- which actually should cause the exit traps to be executed, since exit traps are executed on SIGINT in bash.

So, while trap .. exit is useful for cleanup, there are stil scenarios where it won't be executed, the most obvious ones being power outages and receiving SIGKILL.


I often end up with bash scripts becoming quite large, as I add additional options, or otherwise change them. When a bash-script contains a lot of functions, using 'trap EXIT' may become non-trivial.

For instance, consider a script invoked as

dotask TASK [ARG ...]

where each TASK may consists of substeps, where it is desirable to perform cleanup in between.

In this case, it is helpful to work with subshells to produce scoped exit traps, e.g.

function subTask (
    local tempFile=$(mktemp)
    trap "rm '${tempFile}'" exit
    ...
)

However, working with subshells can be tricky, as they can't set global variables of the parent shell.

Additionally, it is often inconvenient to write a single exit trap. For instance, the cleanup steps may depend on how far a function came before encountering an error. It would be nice to be able to make RAII style cleanup declarations:

function subTask (
    ...
    onExit 'rm tmp.1'
    ...
    onExit 'rm tmp.2'
    ...
)

It would seem obvious to use something like

handlers=""
function onExit { handlers+="$1;"; trap "$handlers" exit; }

to update the trap. But this fails for nested subshells, as it would cause premature execution of the parent shell's handlers. The client code would have to explicitly reset the handlers variable at the beginning of the subshell.

Solutions discussed in [multiple bash traps for the same signal], which patch the trap by using the output from trap -p EXIT will equally fail: Even though subshells don't inherit the EXIT trap, trap -p exit will display the parent shell's handler so, again, manual resetting is needed.

kdb
  • 3,277
  • 20
  • 37