Project

General

Profile

Function to run a command » History » Version 1

Charles Atkinson, 05/01/2020 11:03

1 1 Charles Atkinson
h1. Function to run a command
2 1 Charles Atkinson
3 1 Charles Atkinson
When writing bash with full command error trapping including timeouts, very similar error trapping code is written multiple times.  Here's a solution.
4 1 Charles Atkinson
5 1 Charles Atkinson
h2. Globals
6 1 Charles Atkinson
7 1 Charles Atkinson
* [[Globals used in several of these functions]]
8 1 Charles Atkinson
* cmd is set by caller
9 1 Charles Atkinson
10 1 Charles Atkinson
h2. Called function
11 1 Charles Atkinson
12 1 Charles Atkinson
msg: [[Generalised messaging function]]
13 1 Charles Atkinson
14 1 Charles Atkinson
<pre>
15 1 Charles Atkinson
#!/bin/bash   <- Does nothing but triggers editor syntax highlighting
16 1 Charles Atkinson
17 1 Charles Atkinson
# Copyright (C) 2019 Charles Atkinson
18 1 Charles Atkinson
#
19 1 Charles Atkinson
# This program is free software; you can redistribute it and/or modify
20 1 Charles Atkinson
# it under the terms of the GNU General Public License as published by
21 1 Charles Atkinson
# the Free Software Foundation; either version 2 of the License, or
22 1 Charles Atkinson
# (at your option) any later version.
23 1 Charles Atkinson
#
24 1 Charles Atkinson
# This program is distributed in the hope that it will be useful,
25 1 Charles Atkinson
# but WITHOUT ANY WARRANTY; without even the implied warranty of
26 1 Charles Atkinson
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
27 1 Charles Atkinson
# GNU General Public License for more details.
28 1 Charles Atkinson
#
29 1 Charles Atkinson
# You should have received a copy of the GNU General Public License
30 1 Charles Atkinson
# along with this program; if not, write to the Free Software
31 1 Charles Atkinson
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
32 1 Charles Atkinson
33 1 Charles Atkinson
#--------------------------
34 1 Charles Atkinson
# Name: run_cmd_with_timeout
35 1 Charles Atkinson
# Purpose:
36 1 Charles Atkinson
#   * Optionally checks the configured ssh connection
37 1 Charles Atkinson
#   * Runs the command in array $cmd
38 1 Charles Atkinson
#   * Puts any output of the command in file $out_fn
39 1 Charles Atkinson
#   * Puts any return code from the command in file $rc_fn
40 1 Charles Atkinson
#   * Sets return code:
41 1 Charles Atkinson
#     0 - No warning message was generated by this function
42 1 Charles Atkinson
#     1 - A warning message was generated by this function
43 1 Charles Atkinson
#     2 - Timed out
44 1 Charles Atkinson
#
45 1 Charles Atkinson
# Usage:
46 1 Charles Atkinson
#   * Before calling this function:
47 1 Charles Atkinson
#       * Array ${cmd[*]} must be loaded with a command.
48 1 Charles Atkinson
#         It must be a simple command; cannot include | < > &
49 1 Charles Atkinson
#       * $out_fn and $rc_fn must contain paths of writeable files.
50 1 Charles Atkinson
#         Any existing content in the files will be removed.
51 1 Charles Atkinson
# Syntax:
52 1 Charles Atkinson
#   run_cmd_with_timeout
53 1 Charles Atkinson
#       [-o <rc test list>] [-w <rc test list>] [-e <rc test list>]
54 1 Charles Atkinson
#       [-O <regex>] [-W <regex>] [-E <regex>]
55 1 Charles Atkinson
#       [-s <ssh host>]
56 1 Charles Atkinson
#       [-t <timeout>[,timeout>] [-T <msg class>[,<msg_class>]]
57 1 Charles Atkinson
# Options:
58 1 Charles Atkinson
#   -o <rc test list> OK return codes.
59 1 Charles Atkinson
#      If none of the tests match the return code:
60 1 Charles Atkinson
#          If -e is also used, message class warning is set
61 1 Charles Atkinson
#          Otherwise (-w is also used), message class error is set
62 1 Charles Atkinson
#   -w <rc test list> warning return codes.
63 1 Charles Atkinson
#      If any of the tests match the return code, message class warning is set
64 1 Charles Atkinson
#   -e <rc test list> error return codes.
65 1 Charles Atkinson
#      If any of the tests match the return code, message class error is set
66 1 Charles Atkinson
#   -O specifies regex for OK output
67 1 Charles Atkinson
#   -W specifies regex for warning output
68 1 Charles Atkinson
#   -E specifies regex for error output
69 1 Charles Atkinson
#   -s <[<user@>]ssh host> Test the ssh connection to the ssh host before
70 1 Charles Atkinson
#      running the command.
71 1 Charles Atkinson
#      If <ssh_host> is an entry in the script runner's ~/.ssh/config file, <user@>
72 1 Charles Atkinson
#      is not required
73 1 Charles Atkinson
#   -t <timeout>[,timeout>] Duration before timing out the command and any
74 1 Charles Atkinson
#      remote host connection test
75 1 Charles Atkinson
#   -T <msg class>[,<msg_class>] Class of message to generate on timeout
76 1 Charles Atkinson
#
77 1 Charles Atkinson
# rc_test_list format:
78 1 Charles Atkinson
#   One or more of > < >= <= != == followed by an unsigned integer and
79 1 Charles Atkinson
#   separated by commas.  Example: >8,==2
80 1 Charles Atkinson
#
81 1 Charles Atkinson
# -o -w and -e usage notes:
82 1 Charles Atkinson
#   * When none are specified, return code >0 generates an error message
83 1 Charles Atkinson
#   * When one or more are specified:
84 1 Charles Atkinson
#     If e is specified and its rc list matches, generates an error message
85 1 Charles Atkinson
#     Else if w is specified and its rc list matches, generates a warning message
86 1 Charles Atkinson
#     Else if o is specified and o's rc list does not match:
87 1 Charles Atkinson
#         If e is specified, generates a warning message
88 1 Charles Atkinson
#         Else generates an error message
89 1 Charles Atkinson
#
90 1 Charles Atkinson
# -O -W and -E usage notes ("output" is combined stdout and stderr):
91 1 Charles Atkinson
#   * When none are specified, output is ignored
92 1 Charles Atkinson
#   * When one or more are specified:
93 1 Charles Atkinson
#     If E is specified and its regex matches output,
94 1 Charles Atkinson
#         generates an error message
95 1 Charles Atkinson
#     Else if W is specified and its regex matches output,
96 1 Charles Atkinson
#         generates a warning message
97 1 Charles Atkinson
#     Else if O is specified and O's regex does not match output,
98 1 Charles Atkinson
#         If E is specified, generates a warning message
99 1 Charles Atkinson
#         Else generates an error message
100 1 Charles Atkinson
#
101 1 Charles Atkinson
# Timeout (-t) list format
102 1 Charles Atkinson
#   * Comma separated list
103 1 Charles Atkinson
#   * First member specifies command timeout
104 1 Charles Atkinson
#   * Second member specifies any remote host connection test timeout (-s option)
105 1 Charles Atkinson
#   * Members must be empty or an unsigned integer or float with an optional suffix:
106 1 Charles Atkinson
#         s for seconds (the default)
107 1 Charles Atkinson
#         m for minutes
108 1 Charles Atkinson
#         h for hours
109 1 Charles Atkinson
#         d for days
110 1 Charles Atkinson
#   * Default: 10 for each unspecified member
111 1 Charles Atkinson
#
112 1 Charles Atkinson
# Timeout message class (-T) list format
113 1 Charles Atkinson
#   * Comma separated list
114 1 Charles Atkinson
#   * First member specifies message class on command timeout
115 1 Charles Atkinson
#   * Second member specifies message class on any remote host connection test
116 1 Charles Atkinson
#   * Members must be an empty string, I, W or E
117 1 Charles Atkinson
#   * Default (when -t not used): two empty strings meaning no message is
118 1 Charles Atkinson
#     generated on timeouts
119 1 Charles Atkinson
120 1 Charles Atkinson
# After calling this function caller could:
121 1 Charles Atkinson
#   * Examine this function's return code
122 1 Charles Atkinson
#   * Read the command's output from $out_fn
123 1 Charles Atkinson
#   * Read the command's return code from $rc_fn
124 1 Charles Atkinson
#
125 1 Charles Atkinson
# Example 1
126 1 Charles Atkinson
#   cmd=(ssh "$router_FQDN" file print detail)
127 1 Charles Atkinson
#   run_cmd_with_timeout
128 1 Charles Atkinson
#   case $? in
129 1 Charles Atkinson
#       1 | 2 )
130 1 Charles Atkinson
#           fct "${FUNCNAME[0]}" 'returning 1'
131 1 Charles Atkinson
#           return 1
132 1 Charles Atkinson
#   esac
133 1 Charles Atkinson
#
134 1 Charles Atkinson
# Example 2
135 1 Charles Atkinson
#    msg I "Checking the $user@$hostname ssh connection"
136 1 Charles Atkinson
#    cmd=(ssh "$user@$hostname" 'echo OK')
137 1 Charles Atkinson
#    run_cmd_with_timeout -t 5
138 1 Charles Atkinson
#    case $? in
139 1 Charles Atkinson
#        0 )
140 1 Charles Atkinson
#            msg I 'ssh connection OK'
141 1 Charles Atkinson
#            ;;
142 1 Charles Atkinson
#        1 | 2 )
143 1 Charles Atkinson
#            msg W 'ssh connection check failed'
144 1 Charles Atkinson
#            fct "${FUNCNAME[0]}" 'returning 1'
145 1 Charles Atkinson
#            return 1
146 1 Charles Atkinson
#    esac
147 1 Charles Atkinson
#
148 1 Charles Atkinson
# Global variables read:
149 1 Charles Atkinson
#   cmd
150 1 Charles Atkinson
#   false
151 1 Charles Atkinson
#   msg_lf
152 1 Charles Atkinson
#   out_fn
153 1 Charles Atkinson
#   rc_fn
154 1 Charles Atkinson
#   true
155 1 Charles Atkinson
# Global variables set: none
156 1 Charles Atkinson
# Output:
157 1 Charles Atkinson
#   * stdout and stderr to log or screen, either directly or via msg function
158 1 Charles Atkinson
# Returns: described above
159 1 Charles Atkinson
#--------------------------
160 1 Charles Atkinson
function run_cmd_with_timeout {
161 1 Charles Atkinson
    fct "${FUNCNAME[0]}" "started with arguments $*"
162 1 Charles Atkinson
    local OPTIND    # Required when getopts is called in a function
163 1 Charles Atkinson
    local args opt
164 1 Charles Atkinson
    local opt_e_flag opt_E_flag opt_o_flag opt_O_flag opt_w_flag opt_W_flag
165 1 Charles Atkinson
    local opt_e_rc_list opt_o_rc_list opt_t_arg opt_w_rc_list
166 1 Charles Atkinson
    local opt_E_regex opt_O_regex opt_T_arg opt_W_regex
167 1 Charles Atkinson
    local cmd_timeout cmd_timeout_msg_class ssh_host
168 1 Charles Atkinson
    local connection_timeout connection_timeout_msg_class
169 1 Charles Atkinson
    local -r duration_OK_regex='^\+?[[:digit:]]*\.?[[:digit:]]+(|d|h|m|s)$'
170 1 Charles Atkinson
    local -r timeout_msg_class_OK_regex='^(|E|I|W)$'
171 1 Charles Atkinson
    local array buf emsg msg msg_class my_rc oldIFS out rc rc_for_msg timed_out_flag
172 1 Charles Atkinson
173 1 Charles Atkinson
    # Parse options
174 1 Charles Atkinson
    # ~~~~~~~~~~~~~
175 1 Charles Atkinson
    args=("$@")
176 1 Charles Atkinson
    emsg=
177 1 Charles Atkinson
    emsg_warn_regex=
178 1 Charles Atkinson
    opt_e_flag=$false
179 1 Charles Atkinson
    opt_E_flag=$false
180 1 Charles Atkinson
    opt_e_rc_list=
181 1 Charles Atkinson
    opt_E_regex=
182 1 Charles Atkinson
    opt_o_flag=$false
183 1 Charles Atkinson
    opt_O_flag=$false
184 1 Charles Atkinson
    opt_o_rc_list=
185 1 Charles Atkinson
    opt_O_regex=
186 1 Charles Atkinson
    opt_t_arg=,
187 1 Charles Atkinson
    opt_T_arg=,
188 1 Charles Atkinson
    opt_w_flag=$false
189 1 Charles Atkinson
    opt_W_flag=$false
190 1 Charles Atkinson
    opt_w_rc_list=
191 1 Charles Atkinson
    opt_W_regex=
192 1 Charles Atkinson
    ssh_host=
193 1 Charles Atkinson
    while getopts :e:E:o:O:s:t:T:w:W: opt "$@"
194 1 Charles Atkinson
    do
195 1 Charles Atkinson
        case $opt in
196 1 Charles Atkinson
            e )
197 1 Charles Atkinson
                opt_e_flag=$true
198 1 Charles Atkinson
                opt_e_rc_list=$OPTARG
199 1 Charles Atkinson
                ;;
200 1 Charles Atkinson
            E )
201 1 Charles Atkinson
                opt_E_flag=$true
202 1 Charles Atkinson
                opt_E_regex=$OPTARG
203 1 Charles Atkinson
                ;;
204 1 Charles Atkinson
            o )
205 1 Charles Atkinson
                opt_o_flag=$true
206 1 Charles Atkinson
                opt_o_rc_list=$OPTARG
207 1 Charles Atkinson
                ;;
208 1 Charles Atkinson
            O )
209 1 Charles Atkinson
                opt_O_flag=$true
210 1 Charles Atkinson
                opt_O_regex=$OPTARG
211 1 Charles Atkinson
                ;;
212 1 Charles Atkinson
            s )
213 1 Charles Atkinson
                ssh_host=$OPTARG
214 1 Charles Atkinson
                ;;
215 1 Charles Atkinson
            t )
216 1 Charles Atkinson
                opt_t_arg=$OPTARG
217 1 Charles Atkinson
                ;;
218 1 Charles Atkinson
            T )
219 1 Charles Atkinson
                opt_T_arg=$OPTARG
220 1 Charles Atkinson
                ;;
221 1 Charles Atkinson
            w )
222 1 Charles Atkinson
                opt_w_flag=$true
223 1 Charles Atkinson
                opt_w_rc_list=$OPTARG
224 1 Charles Atkinson
                ;;
225 1 Charles Atkinson
            W )
226 1 Charles Atkinson
                opt_W_flag=$true
227 1 Charles Atkinson
                opt_W_regex=$OPTARG
228 1 Charles Atkinson
                ;;
229 1 Charles Atkinson
            : )
230 1 Charles Atkinson
                emsg+=$msg_lf"Option $OPTARG must have an argument"
231 1 Charles Atkinson
                ;;
232 1 Charles Atkinson
            * )
233 1 Charles Atkinson
                emsg+=$msg_lf"Invalid option '-$OPTARG'"
234 1 Charles Atkinson
        esac
235 1 Charles Atkinson
    done
236 1 Charles Atkinson
    shift $(($OPTIND-1))
237 1 Charles Atkinson
238 1 Charles Atkinson
    # Test for mutually exclusive options
239 1 Charles Atkinson
    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
240 1 Charles Atkinson
    # There are no mutually exclusive options
241 1 Charles Atkinson
242 1 Charles Atkinson
    # Test for mandatory options not set
243 1 Charles Atkinson
    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
244 1 Charles Atkinson
    # There are no mandatory options
245 1 Charles Atkinson
246 1 Charles Atkinson
    # Parse option arguments
247 1 Charles Atkinson
    # ~~~~~~~~~~~~~~~~~~~~~~
248 1 Charles Atkinson
    oldIFS=$IFS
249 1 Charles Atkinson
    IFS=,
250 1 Charles Atkinson
    array=($opt_t_arg)
251 1 Charles Atkinson
    if ((${#array[*]}<3)); then
252 1 Charles Atkinson
        if [[ ${array[0]:=10} =~ $duration_OK_regex ]]; then
253 1 Charles Atkinson
            cmd_timeout=${array[0]}
254 1 Charles Atkinson
        else
255 1 Charles Atkinson
            emsg+=$msg_lf"-t option arg ${array[0]} invalid"
256 1 Charles Atkinson
            emsg+=" (does not match $duration_OK_regex)"
257 1 Charles Atkinson
        fi
258 1 Charles Atkinson
        if [[ ${array[1]:=10} =~ $duration_OK_regex ]]; then
259 1 Charles Atkinson
            connection_timeout=${array[1]}
260 1 Charles Atkinson
        else
261 1 Charles Atkinson
            emsg+=$msg_lf"-t option arg ${array[1]} invalid"
262 1 Charles Atkinson
            emsg+=" (does not match $duration_OK_regex)"
263 1 Charles Atkinson
        fi
264 1 Charles Atkinson
    else
265 1 Charles Atkinson
        emsg+=$msg_lf"-t option: more than two comma-separated values"
266 1 Charles Atkinson
        emsg+=" ($opt_t_arg)"
267 1 Charles Atkinson
    fi
268 1 Charles Atkinson
    array=($opt_T_arg)
269 1 Charles Atkinson
    if ((${#array[*]}<3)); then
270 1 Charles Atkinson
        if [[ ${array[0]} =~ $timeout_msg_class_OK_regex ]]; then
271 1 Charles Atkinson
            cmd_timeout_msg_class=${array[0]}
272 1 Charles Atkinson
        else
273 1 Charles Atkinson
            emsg+=$msg_lf"-T option arg ${array[0]} invalid"
274 1 Charles Atkinson
            emsg+=" (does not match $timeout_msg_class_OK_regex)"
275 1 Charles Atkinson
        fi
276 1 Charles Atkinson
        [[ ${array[1]:-} = '' ]] && array[1]=    # Default
277 1 Charles Atkinson
        if [[ ${array[1]} =~ $timeout_msg_class_OK_regex ]]; then
278 1 Charles Atkinson
            connection_timeout_msg_class=${array[1]}
279 1 Charles Atkinson
        else
280 1 Charles Atkinson
            emsg+=$msg_lf"-t option arg ${array[1]} invalid"
281 1 Charles Atkinson
            emsg+=" (does not match $timeout_msg_class_OK_regex)"
282 1 Charles Atkinson
        fi
283 1 Charles Atkinson
    else
284 1 Charles Atkinson
        emsg+=$msg_lf"-T option: more than two comma-separated values"
285 1 Charles Atkinson
        emsg+=" ($opt_T_arg)"
286 1 Charles Atkinson
    fi
287 1 Charles Atkinson
    IFS=$oldIFS
288 1 Charles Atkinson
289 1 Charles Atkinson
    # Error trap globals
290 1 Charles Atkinson
    # ~~~~~~~~~~~~~~~~~~
291 1 Charles Atkinson
    [[ ${cmd:-} = '' ]] && emsg+=$msg_lf'$cmd is required but is unset or empty'
292 1 Charles Atkinson
    if [[ ${out_fn:-} != '' ]]; then
293 1 Charles Atkinson
        touch "$out_fn" 2>/dev/null    # In case does not exist
294 1 Charles Atkinson
        buf=$(ck_file "$out_fn" f:rw 2>&1)
295 1 Charles Atkinson
        [[ $buf != '' ]] && emsg+=$msg_lf$buf
296 1 Charles Atkinson
    else
297 1 Charles Atkinson
        emsg+=$msg_lf'$out_fn is required but is unset or empty'
298 1 Charles Atkinson
    fi
299 1 Charles Atkinson
    if [[ ${rc_fn:-} != '' ]]; then
300 1 Charles Atkinson
        touch "$rc_fn" 2>/dev/null    # In case does not exist
301 1 Charles Atkinson
        buf=$(ck_file "$rc_fn" f:rw 2>&1)
302 1 Charles Atkinson
        [[ $buf != '' ]] && emsg+=$msg_lf$buf
303 1 Charles Atkinson
    else
304 1 Charles Atkinson
        emsg+=$msg_lf'$rc_fn is required but is unset or empty'
305 1 Charles Atkinson
    fi
306 1 Charles Atkinson
307 1 Charles Atkinson
    # Report any invocation errors
308 1 Charles Atkinson
    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
309 1 Charles Atkinson
    if [[ $emsg != '' ]]; then
310 1 Charles Atkinson
        msg E "Programming error: ${FUNCNAME[0]} (args ${args[*]})$emsg"
311 1 Charles Atkinson
        return 1    # Required when being run from finalise function
312 1 Charles Atkinson
    fi
313 1 Charles Atkinson
    msg D "opt_o_rc_list: $opt_o_rc_list, opt_w_rc_list: $opt_w_rc_list, opt_e_rc_list: $opt_e_rc_list"
314 1 Charles Atkinson
    msg D "opt_O_regex: $opt_O_regex, opt_W_regex: $opt_W_regex, opt_E_regex: $opt_E_regex"
315 1 Charles Atkinson
316 1 Charles Atkinson
    # Check ssh connection if requested
317 1 Charles Atkinson
    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
318 1 Charles Atkinson
    if [[ $ssh_host != '' ]]; then
319 1 Charles Atkinson
        msg="Testing the $ssh_host ssh connection"
320 1 Charles Atkinson
        msg I "$msg with timeout $connection_timeout"
321 1 Charles Atkinson
        echo -n > "$out_fn"; echo -n > "$rc_fn"    # Ensure empty
322 1 Charles Atkinson
        msg_class=
323 1 Charles Atkinson
        timeout --signal=SIGTERM --kill-after=$connection_timeout \
324 1 Charles Atkinson
            $connection_timeout ssh "$ssh_host" echo -n OK > "$out_fn" 2>&1
325 1 Charles Atkinson
        rc=$?
326 1 Charles Atkinson
        if ((rc==124||rc==137)); then
327 1 Charles Atkinson
            [[ $connection_timeout_msg_class != '' ]] \
328 1 Charles Atkinson
                && msg $connection_timeout_msg_class \
329 1 Charles Atkinson
                   'Timed out'
330 1 Charles Atkinson
            fct "${FUNCNAME[0]}" 'returning 2'
331 1 Charles Atkinson
            return 2
332 1 Charles Atkinson
        fi
333 1 Charles Atkinson
        msg I OK
334 1 Charles Atkinson
    fi
335 1 Charles Atkinson
336 1 Charles Atkinson
    # Run the command with timeout
337 1 Charles Atkinson
    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
338 1 Charles Atkinson
    msg I "Running command with timeout $cmd_timeout: ${cmd[*]}"
339 1 Charles Atkinson
    echo -n > "$out_fn"; echo -n > "$rc_fn"    # Ensure empty
340 1 Charles Atkinson
    timeout --signal=SIGTERM --kill-after=$cmd_timeout \
341 1 Charles Atkinson
        $cmd_timeout "${cmd[@]}" > "$out_fn" 2>&1
342 1 Charles Atkinson
    rc=$?
343 1 Charles Atkinson
    if ((rc==124||rc==137)); then
344 1 Charles Atkinson
        timed_out_flag=$true
345 1 Charles Atkinson
        msg I 'Timed out'
346 1 Charles Atkinson
        rc_for_msg='not available (timed out)'
347 1 Charles Atkinson
    else
348 1 Charles Atkinson
        timed_out_flag=$false
349 1 Charles Atkinson
        echo $rc > "$rc_fn"
350 1 Charles Atkinson
        rc_for_msg=$rc
351 1 Charles Atkinson
    fi
352 1 Charles Atkinson
    msg_class=
353 1 Charles Atkinson
354 1 Charles Atkinson
    # Examine any return code if requested
355 1 Charles Atkinson
    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
356 1 Charles Atkinson
    # The return code is not available after timeout
357 1 Charles Atkinson
    # -o -w and -e usage notes:
358 1 Charles Atkinson
    #   * When none are specified, return code >0 generates an error message
359 1 Charles Atkinson
    #   * When one or more are specified:
360 1 Charles Atkinson
    #     If e is specified and its rc list matches, generates an error message
361 1 Charles Atkinson
    #     Else if w is specified and its rc list matches, generates a warning message
362 1 Charles Atkinson
    #     Else if o is specified and o's rc list does not match:
363 1 Charles Atkinson
    #         If e is specified, generates a warning message
364 1 Charles Atkinson
    #         Else generates an error message
365 1 Charles Atkinson
    if [[ ! $timed_out_flag ]]; then
366 1 Charles Atkinson
        msg D "${FUNCNAME[0]}: Examining return code, $rc"
367 1 Charles Atkinson
        if [[ $opt_e_rc_list != '' ]]; then
368 1 Charles Atkinson
            msg D "${FUNCNAME[0]}: -e specified"
369 1 Charles Atkinson
            run_rc_tests $opt_e_rc_list && msg_class=E
370 1 Charles Atkinson
        elif [[ $opt_w_rc_list != '' ]]; then
371 1 Charles Atkinson
            msg D "${FUNCNAME[0]}: -w specified"
372 1 Charles Atkinson
            run_rc_tests $opt_w_rc_list && msg_class=W
373 1 Charles Atkinson
        elif [[ $opt_o_rc_list != '' ]] && ! run_rc_tests $opt_o_rc_list; then
374 1 Charles Atkinson
            msg D "${FUNCNAME[0]}: -o specified and not matched"
375 1 Charles Atkinson
            [[ $opt_e_rc_list = '' ]] && msg_class=W || msg_class=E
376 1 Charles Atkinson
        else
377 1 Charles Atkinson
            msg D "${FUNCNAME[0]}: None of -o -w or -e specified"
378 1 Charles Atkinson
            (($rc>0)) && msg_class=E
379 1 Charles Atkinson
        fi
380 1 Charles Atkinson
        msg D "${FUNCNAME[0]}: After examining return code, msg_class: $msg_class"
381 1 Charles Atkinson
    fi
382 1 Charles Atkinson
383 1 Charles Atkinson
    # Examine any output if requested
384 1 Charles Atkinson
    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
385 1 Charles Atkinson
    # -O -W and -E usage notes ("output" is combined stdout and stderr):
386 1 Charles Atkinson
    #   * When none are specified, output is ignored
387 1 Charles Atkinson
    #   * When one or more are specified:
388 1 Charles Atkinson
    #     If E is specified and its regex matches output,
389 1 Charles Atkinson
    #         generates an error message
390 1 Charles Atkinson
    #     Else if W is specified and its regex matches output,
391 1 Charles Atkinson
    #         generates a warning message
392 1 Charles Atkinson
    #     Else if O is specified and O's regex does not match output,
393 1 Charles Atkinson
    #         If E is specified, generates a warning message
394 1 Charles Atkinson
    #         Else generates an error message
395 1 Charles Atkinson
    out=$(< "$out_fn")
396 1 Charles Atkinson
    if [[ $out != '' ]]; then
397 1 Charles Atkinson
        msg D "${FUNCNAME[0]}: Examining output"
398 1 Charles Atkinson
        if [[ $opt_E_regex != '' && "$out" =~ $opt_E_regex ]]; then
399 1 Charles Atkinson
            msg D "${FUNCNAME[0]}: -E specified and matched"
400 1 Charles Atkinson
            msg_class=E
401 1 Charles Atkinson
        elif [[ $opt_W_regex != '' && "$out" =~ $opt_W_regex ]]; then
402 1 Charles Atkinson
            msg D "${FUNCNAME[0]}: -W specified and matched"
403 1 Charles Atkinson
            [[ $msg_class != E ]] && msg_class=W
404 1 Charles Atkinson
        elif [[ $opt_O_regex != '' ]]; then
405 1 Charles Atkinson
            if [[ ! "$out" =~ $opt_O_regex ]]; then
406 1 Charles Atkinson
                msg D "${FUNCNAME[0]}: -O specified ($opt_O_regex) and not matched"
407 1 Charles Atkinson
                if [[ $opt_E_regex = '' ]]; then
408 1 Charles Atkinson
                    msg D "${FUNCNAME[0]}: -E specified"
409 1 Charles Atkinson
                    [[ $msg_class != E ]] && msg_class=W
410 1 Charles Atkinson
                else
411 1 Charles Atkinson
                    msg D "${FUNCNAME[0]}: -E not specified"
412 1 Charles Atkinson
                    msg_class=E
413 1 Charles Atkinson
                fi
414 1 Charles Atkinson
            fi
415 1 Charles Atkinson
        fi
416 1 Charles Atkinson
        msg D "${FUNCNAME[0]}: After examining output, msg_class: $msg_class"
417 1 Charles Atkinson
    fi
418 1 Charles Atkinson
419 1 Charles Atkinson
    # Generate message if required
420 1 Charles Atkinson
    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
421 1 Charles Atkinson
    if [[ $msg_class != '' ]]; then
422 1 Charles Atkinson
        msg="Command: ${cmd[*]}"
423 1 Charles Atkinson
        msg+=$'\n'"rc: $rc_for_msg"
424 1 Charles Atkinson
        msg+=$'\n'"Output: $out"
425 1 Charles Atkinson
        msg $msg_class "$msg"    # Does not return when msg_class is E
426 1 Charles Atkinson
    fi
427 1 Charles Atkinson
428 1 Charles Atkinson
    # Set my return code
429 1 Charles Atkinson
    # ~~~~~~~~~~~~~~~~~~
430 1 Charles Atkinson
    #   0 - No problem detected with the command
431 1 Charles Atkinson
    #   1 - A problem other than timeout was detected
432 1 Charles Atkinson
    #   2 - The command timed out
433 1 Charles Atkinson
    if [[ $timed_out_flag ]]; then
434 1 Charles Atkinson
        my_rc=2
435 1 Charles Atkinson
    elif [[ $msg_class = W ]]; then
436 1 Charles Atkinson
        my_rc=1
437 1 Charles Atkinson
    else
438 1 Charles Atkinson
        my_rc=0
439 1 Charles Atkinson
    fi
440 1 Charles Atkinson
441 1 Charles Atkinson
    fct "${FUNCNAME[0]}" "returning $my_rc"
442 1 Charles Atkinson
    return $my_rc
443 1 Charles Atkinson
}  #  end of function run_cmd_with_timeout
444 1 Charles Atkinson
445 1 Charles Atkinson
#--------------------------
446 1 Charles Atkinson
# Name: run_rc_tests
447 1 Charles Atkinson
# Purpose:
448 1 Charles Atkinson
#   * Runs return code tests (for run_cmd_with_timeout)
449 1 Charles Atkinson
# Arguments:
450 1 Charles Atkinson
#   $1 - comma separated list of arithmentical tests
451 1 Charles Atkinson
#
452 1 Charles Atkinson
# Global variables read: rc
453 1 Charles Atkinson
# Global variables set: none
454 1 Charles Atkinson
# Output:
455 1 Charles Atkinson
#   * stdout and stderr to log or screen, either directly or via msg function
456 1 Charles Atkinson
# Returns:
457 1 Charles Atkinson
#   0 - $rc matched one of the tests
458 1 Charles Atkinson
#   1 - $rc did not match any of the tests
459 1 Charles Atkinson
#--------------------------
460 1 Charles Atkinson
function run_rc_tests {
461 1 Charles Atkinson
    local array i matched numcom oldIFS one_char rhs two_char
462 1 Charles Atkinson
    local -r two_char_numcom_regex='^(<|>|=|!)=$'
463 1 Charles Atkinson
    local -r one_char_numcom_regex='^(<|>)$'
464 1 Charles Atkinson
    local -r uint_regex='^[[:digit:]]+$'
465 1 Charles Atkinson
466 1 Charles Atkinson
    # Parse the argument
467 1 Charles Atkinson
    # ~~~~~~~~~~~~~~~~~~
468 1 Charles Atkinson
    oldIFS=$IFS
469 1 Charles Atkinson
    IFS=,
470 1 Charles Atkinson
    array=($1)
471 1 Charles Atkinson
    IFS=$oldIFS
472 1 Charles Atkinson
473 1 Charles Atkinson
    # For each test
474 1 Charles Atkinson
    # ~~~~~~~~~~~~~
475 1 Charles Atkinson
    for ((i=0;i<${#array[*]};i++))
476 1 Charles Atkinson
    do
477 1 Charles Atkinson
       # Parse the test
478 1 Charles Atkinson
       # ~~~~~~~~~~~~~~
479 1 Charles Atkinson
       two_char=${array[i]:0:2}
480 1 Charles Atkinson
       one_char=${array[i]:0:1}
481 1 Charles Atkinson
       if [[ $two_char =~ $two_char_numcom_regex ]]; then
482 1 Charles Atkinson
           numcom=$two_char
483 1 Charles Atkinson
           rhs=${array[i]:2}
484 1 Charles Atkinson
       elif [[ $one_char =~ $one_char_numcom_regex ]]; then
485 1 Charles Atkinson
           numcom=$one_char
486 1 Charles Atkinson
           rhs=${array[i]:1}
487 1 Charles Atkinson
       else
488 1 Charles Atkinson
           msg E "Programming error: invalid numerc comparison ${array[i]} in $1"
489 1 Charles Atkinson
       fi
490 1 Charles Atkinson
       [[ ! $rhs =~ $uint_regex ]] \
491 1 Charles Atkinson
           &&  msg E "Programming error: invalid numerc comparison ${array[i]} in $1"
492 1 Charles Atkinson
       msg D "rc: $rc, operator: $numcom, rhs: $rhs"
493 1 Charles Atkinson
494 1 Charles Atkinson
       # Test
495 1 Charles Atkinson
       # ~~~~
496 1 Charles Atkinson
       matched=$false
497 1 Charles Atkinson
       case $numcom in
498 1 Charles Atkinson
           '<=' ) ((rc<=rhs)) && matched=$true ;;
499 1 Charles Atkinson
           '>=' ) ((rc>=rhs)) && matched=$true ;;
500 1 Charles Atkinson
           '==' ) ((rc==rhs)) && matched=$true ;;
501 1 Charles Atkinson
           '!=' ) ((rc!=rhs)) && matched=$true ;;
502 1 Charles Atkinson
           '<'  ) ((rc<rhs))  && matched=$true ;;
503 1 Charles Atkinson
           '>'  ) ((rc>rhs))  && matched=$true ;;
504 1 Charles Atkinson
       esac
505 1 Charles Atkinson
       if [[ $matched ]]; then
506 1 Charles Atkinson
           msg D 'An rc comparison matched, returning 0'
507 1 Charles Atkinson
           return 0
508 1 Charles Atkinson
       fi
509 1 Charles Atkinson
510 1 Charles Atkinson
    done
511 1 Charles Atkinson
512 1 Charles Atkinson
    msg D 'No rc comparisons matched, returning 1'
513 1 Charles Atkinson
    return 1
514 1 Charles Atkinson
}  #  end of function run_rc_tests
515 1 Charles Atkinson
</pre>