Take the 2-minute tour ×
Unix & Linux Stack Exchange is a question and answer site for users of Linux, FreeBSD and other Un*x-like operating systems.. It's 100% free, no registration required.

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) 
share|improve this question
    
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

3 Answers 3

up vote 1 down vote accepted

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

abc()
{
    echo "abc----$1";
}

cda()
{
    echo "cda----$1";
}

safe_rollback=( )

add_rollback()
{
    safe_rollback[${#safe_rollback[*]}]=$1;
}

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

add_rollback cda
add_rollback abc
run_rollback
share|improve this answer

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.

share|improve this answer
    
How would that work for multiple operations? –  Let_Me_Be 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). –  Gilles 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. –  Let_Me_Be 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. –  Gilles 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. –  Let_Me_Be Dec 1 '11 at 16:54

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.

share|improve this answer
    
Unfortunately, the dependencies are dynamic. –  Let_Me_Be Dec 1 '11 at 8:50

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

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