1#!/usr/bin/env bash 2 3GETOPT_BIN=$IN_GETOPT_BIN 4GETOPT_BIN=${GETOPT_BIN:-getopt} 5 6__sleep_amount() { 7 if [ -n "$constant_sleep" ]; then 8 sleep_time=$constant_sleep 9 else 10 #TODO: check for awk 11 #TODO: check if user would rather use one of the other possible dependencies: python, ruby, bc, dc 12 sleep_time=`awk "BEGIN {t = $min_sleep * $(( (1<<($attempts -1)) )); print (t > $max_sleep ? $max_sleep : t)}"` 13 fi 14} 15 16__log_out() { 17 echo "$1" 1>&2 18} 19 20# Parameters: max_tries min_sleep max_sleep constant_sleep fail_script EXECUTION_COMMAND 21retry() 22{ 23 local max_tries="$1"; shift 24 local min_sleep="$1"; shift 25 local max_sleep="$1"; shift 26 local constant_sleep="$1"; shift 27 local fail_script="$1"; shift 28 if [ -n "$VERBOSE" ]; then 29 __log_out "Retry Parameters: max_tries=$max_tries min_sleep=$min_sleep max_sleep=$max_sleep constant_sleep=$constant_sleep" 30 if [ -n "$fail_script" ]; then __log_out "Fail script: $fail_script"; fi 31 __log_out "" 32 __log_out "Execution Command: $*" 33 __log_out "" 34 fi 35 36 local attempts=0 37 local return_code=1 38 39 40 while [[ $return_code -ne 0 && $attempts -le $max_tries ]]; do 41 if [ $attempts -gt 0 ]; then 42 __sleep_amount 43 __log_out "Before retry #$attempts: sleeping $sleep_time seconds" 44 sleep $sleep_time 45 fi 46 47 P="$1" 48 for param in "${@:2}"; do P="$P '$param'"; done 49 #TODO: replace single quotes in each arg with '"'"' ? 50 export RETRY_ATTEMPT=$attempts 51 bash -c "$P" 52 return_code=$? 53 #__log_out "Process returned $return_code on attempt $attempts" 54 if [ $return_code -eq 127 ]; then 55 # command not found 56 exit $return_code 57 elif [ $return_code -ne 0 ]; then 58 attempts=$[$attempts +1] 59 fi 60 done 61 62 if [ $attempts -gt $max_tries ]; then 63 if [ -n "$fail_script" ]; then 64 __log_out "Retries exhausted, running fail script" 65 eval $fail_script 66 else 67 __log_out "Retries exhausted" 68 fi 69 fi 70 71 exit $return_code 72} 73 74# If we're being sourced, don't worry about such things 75if [ "$BASH_SOURCE" == "$0" ]; then 76 # Prints the help text 77 help() 78 { 79 local retry=$(basename $0) 80 cat <<EOF 81Usage: $retry [options] -- execute command 82 -h, -?, --help 83 -v, --verbose Verbose output 84 -t, --tries=# Set max retries: Default 10 85 -s, --sleep=secs Constant sleep amount (seconds) 86 -m, --min=secs Exponential Backoff: minimum sleep amount (seconds): Default 0.3 87 -x, --max=secs Exponential Backoff: maximum sleep amount (seconds): Default 60 88 -f, --fail="script +cmds" Fail Script: run in case of final failure 89EOF 90 } 91 92 # show help for no arguments if stdin is a terminal 93 if { [ -z "$1" ] && [ -t 0 ] ; } || [ "$1" == '-h' ] || [ "$1" == '-?' ] || [ "$1" == '--help' ] 94 then 95 help 96 exit 0 97 fi 98 99 $GETOPT_BIN --test > /dev/null 100 if [[ $? -ne 4 ]]; then 101 echo "I’m sorry, 'getopt --test' failed in this environment. Please load GNU getopt." 102 exit 1 103 fi 104 105 OPTIONS=vt:s:m:x:f: 106 LONGOPTIONS=verbose,tries:,sleep:,min:,max:,fail: 107 108 PARSED=$($GETOPT_BIN --options="$OPTIONS" --longoptions="$LONGOPTIONS" --name "$0" -- "$@") 109 if [[ $? -ne 0 ]]; then 110 # e.g. $? == 1 111 # then getopt has complained about wrong arguments to stdout 112 exit 2 113 fi 114 # read getopt’s output this way to handle the quoting right: 115 eval set -- "$PARSED" 116 117 max_tries=10 118 min_sleep=0.3 119 max_sleep=60.0 120 constant_sleep= 121 fail_script= 122 123 # now enjoy the options in order and nicely split until we see -- 124 while true; do 125 case "$1" in 126 -v|--verbose) 127 VERBOSE=true 128 shift 129 ;; 130 -t|--tries) 131 max_tries="$2" 132 shift 2 133 ;; 134 -s|--sleep) 135 constant_sleep="$2" 136 shift 2 137 ;; 138 -m|--min) 139 min_sleep="$2" 140 shift 2 141 ;; 142 -x|--max) 143 max_sleep="$2" 144 shift 2 145 ;; 146 -f|--fail) 147 fail_script="$2" 148 shift 2 149 ;; 150 --) 151 shift 152 break 153 ;; 154 *) 155 echo "Programming error" 156 exit 3 157 ;; 158 esac 159 done 160 161 retry "$max_tries" "$min_sleep" "$max_sleep" "$constant_sleep" "$fail_script" "$@" 162 163fi 164