I have a script that is composed of staged operations which work mostly in a start/stop/status manner.

The issue I have is that when starting, each of the operations can fail and I need to do a safe rollback using the stop actions.

How could I do this in bash?

I was thinking of something like the following (which doesn't work):

operation1_start() {}
operation2_start() {}
operation1_stop() {}
operation2_stop() {}

operation1_start && rollback=operation1_stop;$rollback
test_validity || $($rollback)
operation2_start && rollback=operation2_stop;$rollback
test_validity || $($rollback) 
  • I don't think you want the extra $() around $rollback. that would be like putting $(operation1_stop) there.
    – user606723
    Nov 30 '11 at 22:55
  • an idea would be to write the rollback commands to a file, then run that file if you need to rollback. ie do_a_thing; echo cmd_to_undo_a_thing >> rollback.sh Mar 7 at 5:48

You will find these two shell programming techniques useful:

  • If you run set -e, then the shell exits immediately if a command returns a non-zero status (except in the cases where it's obviously meant, such as if or while conditionals).
  • If you run trap 'somecode' EXIT, then if the script exits (either explicitly, or implicitly because of set -e), somecode is executed first. On entry into somecode, $? contains the status of the last command.

Thus you can write something like

(set -e; trap 'abort operation a' EXIT; perform operation a; )

In bash, you can set a trap on ERR instead of EXIT; such traps are only executed if the shell exits due to set -e. Furthermore ERR traps are local to functions.

set -e
operation_a () {
  trap 'abort code' ERR
  perform operation a

When you can, it's easier to first prepare a draft and then perform an atomic operation to commit your transaction. For example, if you're writing to a file, write to a temporary file in the destination directory, then call mv to move the new file into place. This is a lot more robust than anything that requires cleanup in the form of rollback code, because the rollback will not be executed if your script dies because of a kill -9 or power failure.

  • How would that work for multiple operations? Dec 1 '11 at 6:47
  • @Let_Me_Be If you can, go for the simplest approach: break up your task into separate transactions, and either commit or abort each. “Commit” means that the transaction (the subtask) completed successfully, and “abort” means that the transaction had no effect (whatever it started to do, it undid). Dec 1 '11 at 11:08
  • Well yes, but when you have transactions A B C and C fails, you need to rollback B and A as well. Dec 1 '11 at 14:28
  • @Let_Me_Be If you need that, then you have nested transactions. The whole ABC sequence is itself a transaction, and in your model you need to rollback the part of ABC that you've committed. Dec 1 '11 at 14:44
  • Yes, I'm asking how to do that in your model. Because what I have is a set of operations that get the system from start state to end state. The are several correct combinations. Dec 1 '11 at 16:54

I actually managed to implement this using arrays. Seems to work fine (I even added an extra parameter telling the functions, that this is a rollback).

    echo "abc----$1";

    echo "cda----$1";

safe_rollback=( )


    while [ ${#safe_rollback[@]} -ge 1 ]; do
        ${safe_rollback[${#safe_rollback[@]}-1]} rollback;
        unset safe_rollback[${#safe_rollback[@]}-1];

add_rollback cda
add_rollback abc

Your operation actually sounds like a clone of a build process, where you have a start/status/continue/rollback/stop type actions based on various situations. You can probably achieve that effect more elegantly if you used Makefiles in conjunction with your shell scripts, because interdependencies that determine state are more elegantly definable there.

  • Unfortunately, the dependencies are dynamic. Dec 1 '11 at 8:50

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.