aboutsummaryrefslogblamecommitdiff
path: root/bin/try
blob: 6efc0aab19b8ec559b1d2e206e7937166f994f34 (plain) (tree)
1
2
3
4
5
6
7
8







                                                                               


                                                                               
 



                                         



                         
                                                                                






                                                        



                                             

                    
      

                 
                               












                                         




                                                        




















                                                                   
              












                                                                               

                                                  

                                                                 


                                                                             



                                                


                                                                               



                                                
                         






                                                                             
#!/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 <tom@sanctum.geek.nz>
# 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"