1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
|
#!/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"
|