Here I show you how to repeat a command until it finishes successfully. This should only be done with programs that support resume and also have legit exit codes (or else they might run on for forever – read on). I show how I constructed such a function, by first showing how to create a function that repeats rsync only. Then we build a function that can repeat anything.
Sometimes you will run a command and it will fail. However you can repeat that same command to resume it where it left off. A good example is “rsync”. rsync can be made to resume from where it left off – I use the partial option (-P gives progress bar and partial option at the same time). This can be done with any command that supports resume and has good exit codes (meaning success is 0, and fails are none 0).
Two criteria for this to work
- supports resume in a good way (cp supports resume, but its not good – see below, rsync supports good resume)
- has good exit codes (0 is success, none-zero for none-success)
Note that “cp” is not a good example of a command that can resume where it left off (even if you use -u), because it will skip the files that where partially copied. So if you have 10 big files, and the “cp” got cut off mid 5th file, when you repeat the command, it will just continue with the 6th file. The 5th file will be incomplete.
We will use a recursive method to do this repeating resume operation.
RRSYNC – Repeating Rsync
First I will show how to do this with rsync. We basically run rsync, then if its exit code is zero we exit out, if its exit code is none zero we repeat the function.
function rrsync () { # rrsync 1 <rsync options> # dont forget the -P option as that supports resume # example: rrsync 1 -avhP <src> <dest> N="$1" # this is the run number (it should start off as 1) echo "[`date`|`date +%s`] - rsync run # $1" # arg 1 is the run Number shift; # everything after the run Number is the rest of the args rsync $@ && { # if success we inform and exit/return with 0 echo "rsync successful. rrsync complete in $N run[s]."; return 0; } || { # if fails sleep 1; # sleep 1 second, this allows time for use to Ctrl-C N=$((N+1)); # increase N by 1 as we are going to the next run rrsync $N $@; # repeat this command with same args (except N is +1) } }
Check the comments for how to run it. Basically you would first copy paste that function into a shell and then run this rrsync 1 -avhP <src> <dest> . Also if you had the above code in a script file. Such as rrsync.sh. You would need to source the script first to get that function to be available in the shell source rrsync.sh or the shorthand way . rrsync.sh
Note I decided to keep track of a variable (so that I can show which run number this is), and the only way to do that within a recursive function is with global variables, or a variable that you pass on to the next level. Since I didnt want to use global variables, I just passed it on to the next level, the caveat is that we have to specify the start value of this variable when we begin (hence the 1 before the arguments)
REPEAT function
The next code allows me to repeat any function. I didnt want the user to worry about having to provide 1 all the time, so I created a starting function called “repeat”, which calls the code that does everything “repeatrecurse”.
function repeatrecurse () { # usage: repeatrecurse 0 <cmd> [cmd's options] # example: repeatrecurse 0 rsync -avhP <src> <dest> N="$1"; THEPROG="$2" echo "[`date`|`date +%s`] - $THEPROG run # $N" shift; shift; # the first 2 variables are run # & prog name, the rest are the args for the program "$THEPROG" $@ && { echo "repeat complete in $N run[s]. $THEPROG successful."; return 0; } || { sleep 1; N=$((N+1)); repeatrecurse "$N" "$THEPROG" "$@"; } } function repeat () { # usage: repeat <command> [commands arguments] # example: repeat rsync -avhP <src> <dest> repeatrecurse 1 $@ # this inputs starting run #, prog name, & progs args into repeatrecurse function return 0 }
So copy& paste the above two functions into a shell to source them (or source them thru a script with the source bash builtin), so that you can use repeat. Then to utilize the repeat function do this:
repeat rsync -avhP <src> <dst>
Rsync will then repeat until success.
You can test the code by creating two small functions one which always returns success (exit code 0, so it will only run once), and one that will always return none-success (none-zero exit code, so it will run forever until someone presses Control-C or kills it)
# testrepeat0 always exits with 0 (success) # testrepeat1 always exits with 1 (since not 0, none success) function testrepeat0 () { echo " testrepeat0 options: $@" sleep 1 echo " testrepeat0 done" return 0 } function testrepeat1 () { echo " testrepeat1 options: $@" sleep 1 echo " testrepeat1 done" return 1 }
You can test them like so
Here we show how it runs once when we have a successful end
$ repeat testrepeat0 everything is awesome [Fri May 26 04:09:30 PDT 2017|1495796971] - testrepeat0 run # 1 testrepeat0 options: everything is awesome testrepeat0 done repeat complete. testrepeat0 successful.
Here we show how it repeats
$ repeat testrepeat1 when your part of a team [Fri May 26 04:10:38 PDT 2017|1495797039] - testrepeat1 run # 1 testrepeat1 options: when your part of a team testrepeat1 done [Fri May 26 04:10:41 PDT 2017|1495797042] - testrepeat1 run # 2 testrepeat1 options: when your part of a team testrepeat1 done [Fri May 26 04:10:44 PDT 2017|1495797044] - testrepeat1 run # 3 testrepeat1 options: when your part of a team testrepeat1 done [Fri May 26 04:10:46 PDT 2017|1495797047] - testrepeat1 run # 4 testrepeat1 options: when your part of a team testrepeat1 done
The end