Project

General

Profile

Function to run a command » History » Version 2

Charles Atkinson, 05/01/2020 11:12
Added {{toc}} and h1. Introduction

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