#!/usr/bin/env bash # # try(1) -- Attempt a certain number of times to perform a task, stopping after # the first success, and only print collected stderr if all the attempts # failed. Designed for running from systems like cron(8) where blips and # short-term failures can be ignored. # # -h gives help, -v gives you diagnostics on stdout, -i sets an optional number # of seconds between each attempt, -n sets the number of attempts (defaults to # 3). # # Author: Tom Ryder # Copyright: 2016 # License: Public domain # self=try # Print usage information usage() { printf '%s: usage: %s [-hv] [-i INTERVAL] [-n ATTEMPTS] [--] COMMAND...\n' \ "$self" "$self" } # Flag for whether to print diagnostics to stdout or not declare -i verbose verbose=0 # Number of seconds to wait between instances declare -i interval interval=0 # Number of attempts declare -i attc attc=3 # Process options while getopts 'hvi:n:' opt ; do case $opt in # -h: Print help h) usage exit 0 ;; # -v: Print diagnostics to stdout v) verbose=1 ;; # -i: Set the number of seconds between attempts i) interval=$OPTARG ;; # -n: Set the number of attempts n) attc=$OPTARG ;; # Unknown option \?) usage >&2 exit 2 ;; esac done shift "$((OPTIND-1))" # We need at least one more argument after shifting off the options if ! (($#)) ; then usage >&2 exit 2 fi # The command is all the remaining arguments declare -a cmd cmd=("$@") # Create a buffer file for the error output, and clean up the file when we exit errbuf=$(mktemp) || exit cleanup() { rm -f -- "$errbuf" } trap cleanup EXIT # Keep trying the command, writing error output to the buffer file, and exit # if we succeed on any of them declare -i ret for (( atti = 1 ; atti <= attc ; atti++ )) ; do # If verbose, print the number of this attempt ((verbose)) && printf '%s: Attempt %u/%u to run `%s` ...\n' \ "$self" "$atti" "$attc" "${cmd[*]}" # Try running the command. If it succeeds, report failure if verbose, and # exit 0. if "${cmd[@]}" 2>>"$errbuf" ; then ((verbose)) && printf '%s: Success!\n' \ "$self" exit 0 # If it fails, keep the exit value, report failure, and wait until the next # attempt. else ret=$? ((verbose)) && printf '%s: Failure!\n' \ "$self" sleep "$interval" fi done # Attempts were exhausted, and all failed; print the error output from all of # the failures and exit with the non-zero exit value of the most recent one cat -- "$errbuf" >&2 exit "$ret"