1e7e9171eSGeorge Keishing#!/usr/bin/env python3
2c3b512e5SMichael Walsh
3c3b512e5SMichael Walshr"""
4c3b512e5SMichael WalshThis module provides command execution functions such as cmd_fnc and cmd_fnc_u.
5c3b512e5SMichael Walsh"""
6c3b512e5SMichael Walsh
7e635ddc0SGeorge Keishingimport collections
8e635ddc0SGeorge Keishingimport inspect
920f38712SPatrick Williamsimport os
1020f38712SPatrick Williamsimport re
1120f38712SPatrick Williamsimport signal
1220f38712SPatrick Williamsimport subprocess
1320f38712SPatrick Williamsimport sys
1420f38712SPatrick Williamsimport time
15c3b512e5SMichael Walsh
1620f38712SPatrick Williamsimport func_args as fa
1720f38712SPatrick Williamsimport gen_misc as gm
18c3b512e5SMichael Walshimport gen_print as gp
19c3b512e5SMichael Walshimport gen_valid as gv
20d690150cSMichael Walsh
21d690150cSMichael Walshrobot_env = gp.robot_env
22d690150cSMichael Walsh
23afc53a2cSMichael Walshif robot_env:
24a3e2f533SMichael Walsh    from robot.libraries.BuiltIn import BuiltIn
25c3b512e5SMichael Walsh
26c3b512e5SMichael Walsh
279e042ad3SMichael Walsh# cmd_fnc and cmd_fnc_u should now be considered deprecated.  shell_cmd and t_shell_cmd should be used
289e042ad3SMichael Walsh# instead.
2920f38712SPatrick Williamsdef cmd_fnc(
3020f38712SPatrick Williams    cmd_buf,
31c3b512e5SMichael Walsh    quiet=None,
32c3b512e5SMichael Walsh    test_mode=None,
33afc53a2cSMichael Walsh    debug=0,
34c3b512e5SMichael Walsh    print_output=1,
35cfe9fedcSMichael Walsh    show_err=1,
36a3e2f533SMichael Walsh    return_stderr=0,
3720f38712SPatrick Williams    ignore_err=1,
3820f38712SPatrick Williams):
39c3b512e5SMichael Walsh    r"""
409e042ad3SMichael Walsh    Run the given command in a shell and return the shell return code and the output.
41c3b512e5SMichael Walsh
42c3b512e5SMichael Walsh    Description of arguments:
43c3b512e5SMichael Walsh    cmd_buf                         The command string to be run in a shell.
449e042ad3SMichael Walsh    quiet                           Indicates whether this function should run the print_issuing() function
459e042ad3SMichael Walsh                                    which prints "Issuing: <cmd string>" to stdout.
469e042ad3SMichael Walsh    test_mode                       If test_mode is set, this function will not actually run the command.  If
479e042ad3SMichael Walsh                                    print_output is set, it will print "(test_mode) Issuing: <cmd string>" to
48cfe9fedcSMichael Walsh                                    stdout.
499e042ad3SMichael Walsh    debug                           If debug is set, this function will print extra debug info.
509e042ad3SMichael Walsh    print_output                    If this is set, this function will print the stdout/stderr generated by
519e042ad3SMichael Walsh                                    the shell command.
529e042ad3SMichael Walsh    show_err                        If show_err is set, this function will print a standardized error report
539e042ad3SMichael Walsh                                    if the shell command returns non-zero.
549e042ad3SMichael Walsh    return_stderr                   If return_stderr is set, this function will process the stdout and stderr
559e042ad3SMichael Walsh                                    streams from the shell command separately.  It will also return stderr in
569e042ad3SMichael Walsh                                    addition to the return code and the stdout.
57c3b512e5SMichael Walsh    """
58c3b512e5SMichael Walsh
59cfe9fedcSMichael Walsh    # Determine default values.
60c3b512e5SMichael Walsh    quiet = int(gm.global_default(quiet, 0))
61c3b512e5SMichael Walsh    test_mode = int(gm.global_default(test_mode, 0))
62c3b512e5SMichael Walsh
63c3b512e5SMichael Walsh    if debug:
64c3b512e5SMichael Walsh        gp.print_vars(cmd_buf, quiet, test_mode, debug)
65c3b512e5SMichael Walsh
66ec01a6f6SMichael Walsh    err_msg = gv.valid_value(cmd_buf)
67c3b512e5SMichael Walsh    if err_msg != "":
68c3b512e5SMichael Walsh        raise ValueError(err_msg)
69c3b512e5SMichael Walsh
70c3b512e5SMichael Walsh    if not quiet:
71c3b512e5SMichael Walsh        gp.pissuing(cmd_buf, test_mode)
72c3b512e5SMichael Walsh
73c3b512e5SMichael Walsh    if test_mode:
74cfe9fedcSMichael Walsh        if return_stderr:
75cfe9fedcSMichael Walsh            return 0, "", ""
76cfe9fedcSMichael Walsh        else:
77c3b512e5SMichael Walsh            return 0, ""
78c3b512e5SMichael Walsh
79cfe9fedcSMichael Walsh    if return_stderr:
80cfe9fedcSMichael Walsh        err_buf = ""
81cfe9fedcSMichael Walsh        stderr = subprocess.PIPE
82cfe9fedcSMichael Walsh    else:
83cfe9fedcSMichael Walsh        stderr = subprocess.STDOUT
84cfe9fedcSMichael Walsh
8520f38712SPatrick Williams    sub_proc = subprocess.Popen(
8620f38712SPatrick Williams        cmd_buf,
87c3b512e5SMichael Walsh        bufsize=1,
88c3b512e5SMichael Walsh        shell=True,
89e7e9171eSGeorge Keishing        universal_newlines=True,
9020f38712SPatrick Williams        executable="/bin/bash",
91c3b512e5SMichael Walsh        stdout=subprocess.PIPE,
9220f38712SPatrick Williams        stderr=stderr,
9320f38712SPatrick Williams    )
94c3b512e5SMichael Walsh    out_buf = ""
95cfe9fedcSMichael Walsh    if return_stderr:
96cfe9fedcSMichael Walsh        for line in sub_proc.stderr:
9736efbc04SGeorge Keishing            try:
9836efbc04SGeorge Keishing                err_buf += line
9936efbc04SGeorge Keishing            except TypeError:
10036efbc04SGeorge Keishing                line = line.decode("utf-8")
101cfe9fedcSMichael Walsh                err_buf += line
102cfe9fedcSMichael Walsh            if not print_output:
103cfe9fedcSMichael Walsh                continue
104c108e429SMichael Walsh            gp.gp_print(line)
105c3b512e5SMichael Walsh    for line in sub_proc.stdout:
10636efbc04SGeorge Keishing        try:
10736efbc04SGeorge Keishing            out_buf += line
10836efbc04SGeorge Keishing        except TypeError:
10936efbc04SGeorge Keishing            line = line.decode("utf-8")
110c3b512e5SMichael Walsh            out_buf += line
111c3b512e5SMichael Walsh        if not print_output:
112c3b512e5SMichael Walsh            continue
113c108e429SMichael Walsh        gp.gp_print(line)
114c3b512e5SMichael Walsh    if print_output and not robot_env:
115c3b512e5SMichael Walsh        sys.stdout.flush()
116c3b512e5SMichael Walsh    sub_proc.communicate()
117c3b512e5SMichael Walsh    shell_rc = sub_proc.returncode
118a3e2f533SMichael Walsh    if shell_rc != 0:
119a3e2f533SMichael Walsh        err_msg = "The prior shell command failed.\n"
1201429e127SMichael Walsh        err_msg += gp.sprint_var(shell_rc, gp.hexa())
121cfe9fedcSMichael Walsh        if not print_output:
122cfe9fedcSMichael Walsh            err_msg += "out_buf:\n" + out_buf
123c3b512e5SMichael Walsh
124a3e2f533SMichael Walsh        if show_err:
125cfe9fedcSMichael Walsh            gp.print_error_report(err_msg)
126a3e2f533SMichael Walsh        if not ignore_err:
127a3e2f533SMichael Walsh            if robot_env:
128a3e2f533SMichael Walsh                BuiltIn().fail(err_msg)
129a3e2f533SMichael Walsh            else:
130a3e2f533SMichael Walsh                raise ValueError(err_msg)
131cfe9fedcSMichael Walsh
132cfe9fedcSMichael Walsh    if return_stderr:
133cfe9fedcSMichael Walsh        return shell_rc, out_buf, err_buf
134cfe9fedcSMichael Walsh    else:
135c3b512e5SMichael Walsh        return shell_rc, out_buf
136c3b512e5SMichael Walsh
137c3b512e5SMichael Walsh
13820f38712SPatrick Williamsdef cmd_fnc_u(
13920f38712SPatrick Williams    cmd_buf,
140c3b512e5SMichael Walsh    quiet=None,
141c3b512e5SMichael Walsh    debug=None,
142c3b512e5SMichael Walsh    print_output=1,
143cfe9fedcSMichael Walsh    show_err=1,
144a3e2f533SMichael Walsh    return_stderr=0,
14520f38712SPatrick Williams    ignore_err=1,
14620f38712SPatrick Williams):
147c3b512e5SMichael Walsh    r"""
148c3b512e5SMichael Walsh    Call cmd_fnc with test_mode=0.  See cmd_fnc (above) for details.
149c3b512e5SMichael Walsh
150c3b512e5SMichael Walsh    Note the "u" in "cmd_fnc_u" stands for "unconditional".
151c3b512e5SMichael Walsh    """
152c3b512e5SMichael Walsh
15320f38712SPatrick Williams    return cmd_fnc(
15420f38712SPatrick Williams        cmd_buf,
15520f38712SPatrick Williams        test_mode=0,
15620f38712SPatrick Williams        quiet=quiet,
15720f38712SPatrick Williams        debug=debug,
15820f38712SPatrick Williams        print_output=print_output,
15920f38712SPatrick Williams        show_err=show_err,
16020f38712SPatrick Williams        return_stderr=return_stderr,
16120f38712SPatrick Williams        ignore_err=ignore_err,
16220f38712SPatrick Williams    )
163c3b512e5SMichael Walsh
164f41fac81SMichael Walsh
165f41fac81SMichael Walshdef parse_command_string(command_string):
166f41fac81SMichael Walsh    r"""
1679e042ad3SMichael Walsh    Parse a bash command-line command string and return the result as a dictionary of parms.
168f41fac81SMichael Walsh
1699e042ad3SMichael Walsh    This can be useful for answering questions like "What did the user specify as the value for parm x in the
1709e042ad3SMichael Walsh    command string?".
171f41fac81SMichael Walsh
1729e042ad3SMichael Walsh    This function expects the command string to follow the following posix conventions:
173f41fac81SMichael Walsh    - Short parameters:
174f41fac81SMichael Walsh      -<parm name><space><arg value>
175f41fac81SMichael Walsh    - Long parameters:
176f41fac81SMichael Walsh      --<parm name>=<arg value>
177f41fac81SMichael Walsh
1789e042ad3SMichael Walsh    The first item in the string will be considered to be the command.  All values not conforming to the
1799e042ad3SMichael Walsh    specifications above will be considered positional parms.  If there are multiple parms with the same
1809e042ad3SMichael Walsh    name, they will be put into a list (see illustration below where "-v" is specified multiple times).
181f41fac81SMichael Walsh
182f41fac81SMichael Walsh    Description of argument(s):
1839e042ad3SMichael Walsh    command_string                  The complete command string including all parameters and arguments.
184f41fac81SMichael Walsh
185f41fac81SMichael Walsh    Sample input:
186f41fac81SMichael Walsh
1879e042ad3SMichael Walsh    robot_cmd_buf:                                    robot -v OPENBMC_HOST:dummy1 -v keyword_string:'Set
1889e042ad3SMichael Walsh    Auto Reboot  no' -v lib_file_path:/home/user1/git/openbmc-test-automation/lib/utils.robot -v quiet:0 -v
1899e042ad3SMichael Walsh    test_mode:0 -v debug:0 --outputdir='/home/user1/status/children/'
1909e042ad3SMichael Walsh    --output=dummy1.Auto_reboot.170802.124544.output.xml --log=dummy1.Auto_reboot.170802.124544.log.html
191f41fac81SMichael Walsh    --report=dummy1.Auto_reboot.170802.124544.report.html
192f41fac81SMichael Walsh    /home/user1/git/openbmc-test-automation/extended/run_keyword.robot
193f41fac81SMichael Walsh
194f41fac81SMichael Walsh    Sample output:
195f41fac81SMichael Walsh
196f41fac81SMichael Walsh    robot_cmd_buf_dict:
197f41fac81SMichael Walsh      robot_cmd_buf_dict[command]:                    robot
198f41fac81SMichael Walsh      robot_cmd_buf_dict[v]:
199f41fac81SMichael Walsh        robot_cmd_buf_dict[v][0]:                     OPENBMC_HOST:dummy1
2009e042ad3SMichael Walsh        robot_cmd_buf_dict[v][1]:                     keyword_string:Set Auto Reboot no
201f41fac81SMichael Walsh        robot_cmd_buf_dict[v][2]:
202f41fac81SMichael Walsh        lib_file_path:/home/user1/git/openbmc-test-automation/lib/utils.robot
203f41fac81SMichael Walsh        robot_cmd_buf_dict[v][3]:                     quiet:0
204f41fac81SMichael Walsh        robot_cmd_buf_dict[v][4]:                     test_mode:0
205f41fac81SMichael Walsh        robot_cmd_buf_dict[v][5]:                     debug:0
2069e042ad3SMichael Walsh      robot_cmd_buf_dict[outputdir]:                  /home/user1/status/children/
2079e042ad3SMichael Walsh      robot_cmd_buf_dict[output]:                     dummy1.Auto_reboot.170802.124544.output.xml
2089e042ad3SMichael Walsh      robot_cmd_buf_dict[log]:                        dummy1.Auto_reboot.170802.124544.log.html
2099e042ad3SMichael Walsh      robot_cmd_buf_dict[report]:                     dummy1.Auto_reboot.170802.124544.report.html
210f41fac81SMichael Walsh      robot_cmd_buf_dict[positional]:
211f41fac81SMichael Walsh      /home/user1/git/openbmc-test-automation/extended/run_keyword.robot
212f41fac81SMichael Walsh    """
213f41fac81SMichael Walsh
2149e042ad3SMichael Walsh    # We want the parms in the string broken down the way bash would do it, so we'll call upon bash to do
2159e042ad3SMichael Walsh    # that by creating a simple inline bash function.
21620f38712SPatrick Williams    bash_func_def = (
21720f38712SPatrick Williams        'function parse { for parm in "${@}" ; do' + " echo $parm ; done ; }"
21820f38712SPatrick Williams    )
219f41fac81SMichael Walsh
22020f38712SPatrick Williams    rc, outbuf = cmd_fnc_u(
22120f38712SPatrick Williams        bash_func_def + " ; parse " + command_string, quiet=1, print_output=0
22220f38712SPatrick Williams    )
223f41fac81SMichael Walsh    command_string_list = outbuf.rstrip("\n").split("\n")
224f41fac81SMichael Walsh
225f41fac81SMichael Walsh    command_string_dict = collections.OrderedDict()
226f41fac81SMichael Walsh    ix = 1
22720f38712SPatrick Williams    command_string_dict["command"] = command_string_list[0]
228f41fac81SMichael Walsh    while ix < len(command_string_list):
229f41fac81SMichael Walsh        if command_string_list[ix].startswith("--"):
230f41fac81SMichael Walsh            key, value = command_string_list[ix].split("=")
231f41fac81SMichael Walsh            key = key.lstrip("-")
232f41fac81SMichael Walsh        elif command_string_list[ix].startswith("-"):
233f41fac81SMichael Walsh            key = command_string_list[ix].lstrip("-")
234f41fac81SMichael Walsh            ix += 1
235f41fac81SMichael Walsh            try:
236f41fac81SMichael Walsh                value = command_string_list[ix]
237f41fac81SMichael Walsh            except IndexError:
238f41fac81SMichael Walsh                value = ""
239f41fac81SMichael Walsh        else:
24020f38712SPatrick Williams            key = "positional"
241f41fac81SMichael Walsh            value = command_string_list[ix]
242f41fac81SMichael Walsh        if key in command_string_dict:
243004ad3c9SJoy Onyerikwu            if isinstance(command_string_dict[key], str):
244f41fac81SMichael Walsh                command_string_dict[key] = [command_string_dict[key]]
245f41fac81SMichael Walsh            command_string_dict[key].append(value)
246f41fac81SMichael Walsh        else:
247f41fac81SMichael Walsh            command_string_dict[key] = value
248f41fac81SMichael Walsh        ix += 1
249f41fac81SMichael Walsh
250f41fac81SMichael Walsh    return command_string_dict
25121083d28SMichael Walsh
25221083d28SMichael Walsh
25321083d28SMichael Walsh# Save the original SIGALRM handler for later restoration by shell_cmd.
25421083d28SMichael Walshoriginal_sigalrm_handler = signal.getsignal(signal.SIGALRM)
25521083d28SMichael Walsh
25621083d28SMichael Walsh
25720f38712SPatrick Williamsdef shell_cmd_timed_out(signal_number, frame):
25821083d28SMichael Walsh    r"""
25921083d28SMichael Walsh    Handle an alarm signal generated during the shell_cmd function.
26021083d28SMichael Walsh    """
26121083d28SMichael Walsh
26221083d28SMichael Walsh    gp.dprint_executing()
2633fb26180SMichael Walsh    global command_timed_out
2643fb26180SMichael Walsh    command_timed_out = True
26521083d28SMichael Walsh    # Get subprocess pid from shell_cmd's call stack.
26620f38712SPatrick Williams    sub_proc = gp.get_stack_var("sub_proc", 0)
26721083d28SMichael Walsh    pid = sub_proc.pid
2683fb26180SMichael Walsh    gp.dprint_var(pid)
2693fb26180SMichael Walsh    # Terminate the child process group.
2703fb26180SMichael Walsh    os.killpg(pid, signal.SIGKILL)
27121083d28SMichael Walsh    # Restore the original SIGALRM handler.
27221083d28SMichael Walsh    signal.signal(signal.SIGALRM, original_sigalrm_handler)
27321083d28SMichael Walsh
27421083d28SMichael Walsh    return
27521083d28SMichael Walsh
27621083d28SMichael Walsh
27720f38712SPatrick Williamsdef shell_cmd(
27820f38712SPatrick Williams    command_string,
27921083d28SMichael Walsh    quiet=None,
28055abd1b1SMichael Walsh    print_output=None,
28121083d28SMichael Walsh    show_err=1,
28221083d28SMichael Walsh    test_mode=0,
28321083d28SMichael Walsh    time_out=None,
28421083d28SMichael Walsh    max_attempts=1,
28521083d28SMichael Walsh    retry_sleep_time=5,
2863fb26180SMichael Walsh    valid_rcs=[0],
28721083d28SMichael Walsh    ignore_err=None,
288faafa9c5SMichael Walsh    return_stderr=0,
2899e042ad3SMichael Walsh    fork=0,
29020f38712SPatrick Williams    error_regexes=None,
29120f38712SPatrick Williams):
29221083d28SMichael Walsh    r"""
2939e042ad3SMichael Walsh    Run the given command string in a shell and return a tuple consisting of the shell return code and the
2949e042ad3SMichael Walsh    output.
29521083d28SMichael Walsh
29621083d28SMichael Walsh    Description of argument(s):
2979e042ad3SMichael Walsh    command_string                  The command string to be run in a shell (e.g. "ls /tmp").
2989e042ad3SMichael Walsh    quiet                           If set to 0, this function will print "Issuing: <cmd string>" to stdout.
2999e042ad3SMichael Walsh                                    When the quiet argument is set to None, this function will assign a
3009e042ad3SMichael Walsh                                    default value by searching upward in the stack for the quiet variable
3019e042ad3SMichael Walsh                                    value.  If no such value is found, quiet is set to 0.
3029e042ad3SMichael Walsh    print_output                    If this is set, this function will print the stdout/stderr generated by
3039e042ad3SMichael Walsh                                    the shell command to stdout.
3049e042ad3SMichael Walsh    show_err                        If show_err is set, this function will print a standardized error report
3059e042ad3SMichael Walsh                                    if the shell command fails (i.e. if the shell command returns a shell_rc
3069e042ad3SMichael Walsh                                    that is not in valid_rcs).  Note: Error text is only printed if ALL
3079e042ad3SMichael Walsh                                    attempts to run the command_string fail.  In other words, if the command
3089e042ad3SMichael Walsh                                    execution is ultimately successful, initial failures are hidden.
3099e042ad3SMichael Walsh    test_mode                       If test_mode is set, this function will not actually run the command.  If
3109e042ad3SMichael Walsh                                    print_output is also set, this function will print "(test_mode) Issuing:
3119e042ad3SMichael Walsh                                    <cmd string>" to stdout.  A caller should call shell_cmd directly if they
3129e042ad3SMichael Walsh                                    wish to have the command string run unconditionally.  They should call
3139e042ad3SMichael Walsh                                    the t_shell_cmd wrapper (defined below) if they wish to run the command
3149e042ad3SMichael Walsh                                    string only if the prevailing test_mode variable is set to 0.
3159e042ad3SMichael Walsh    time_out                        A time-out value expressed in seconds.  If the command string has not
3169e042ad3SMichael Walsh                                    finished executing within <time_out> seconds, it will be halted and
3179e042ad3SMichael Walsh                                    counted as an error.
3189e042ad3SMichael Walsh    max_attempts                    The max number of attempts that should be made to run the command string.
3199e042ad3SMichael Walsh    retry_sleep_time                The number of seconds to sleep between attempts.
3209e042ad3SMichael Walsh    valid_rcs                       A list of integers indicating which shell_rc values are not to be
3219e042ad3SMichael Walsh                                    considered errors.
3229e042ad3SMichael Walsh    ignore_err                      Ignore error means that a failure encountered by running the command
3239e042ad3SMichael Walsh                                    string will not be raised as a python exception.  When the ignore_err
3249e042ad3SMichael Walsh                                    argument is set to None, this function will assign a default value by
3259e042ad3SMichael Walsh                                    searching upward in the stack for the ignore_err variable value.  If no
3269e042ad3SMichael Walsh                                    such value is found, ignore_err is set to 1.
3279e042ad3SMichael Walsh    return_stderr                   If return_stderr is set, this function will process the stdout and stderr
3289e042ad3SMichael Walsh                                    streams from the shell command separately.  In such a case, the tuple
3299e042ad3SMichael Walsh                                    returned by this function will consist of three values rather than just
3309e042ad3SMichael Walsh                                    two: rc, stdout, stderr.
3319e042ad3SMichael Walsh    fork                            Run the command string asynchronously (i.e. don't wait for status of the
3329e042ad3SMichael Walsh                                    child process and don't try to get stdout/stderr) and return the Popen
3339e042ad3SMichael Walsh                                    object created by the subprocess.popen() function.  See the kill_cmd
3349e042ad3SMichael Walsh                                    function for details on how to process the popen object.
3359e042ad3SMichael Walsh    error_regexes                   A list of regular expressions to be used to identify errors in the
3369e042ad3SMichael Walsh                                    command output.  If there is a match for any of these regular
3379e042ad3SMichael Walsh                                    expressions, the command will be considered a failure and the shell_rc
3389e042ad3SMichael Walsh                                    will be set to -1.  For example, if error_regexes = ['ERROR:'] and the
3399e042ad3SMichael Walsh                                    command output contains 'ERROR:  Unrecognized option', it will be counted
3409e042ad3SMichael Walsh                                    as an error even if the command returned 0.  This is useful when running
3419e042ad3SMichael Walsh                                    commands that do not always return non-zero on error.
34221083d28SMichael Walsh    """
34321083d28SMichael Walsh
3443fb26180SMichael Walsh    err_msg = gv.valid_value(command_string)
3453fb26180SMichael Walsh    if err_msg:
3463fb26180SMichael Walsh        raise ValueError(err_msg)
3473fb26180SMichael Walsh
34821083d28SMichael Walsh    # Assign default values to some of the arguments to this function.
34920f38712SPatrick Williams    quiet = int(gm.dft(quiet, gp.get_stack_var("quiet", 0)))
35055abd1b1SMichael Walsh    print_output = int(gm.dft(print_output, not quiet))
35145fead49SMichael Walsh    show_err = int(show_err)
35220f38712SPatrick Williams    ignore_err = int(gm.dft(ignore_err, gp.get_stack_var("ignore_err", 1)))
35321083d28SMichael Walsh
3543fb26180SMichael Walsh    gp.qprint_issuing(command_string, test_mode)
35521083d28SMichael Walsh    if test_mode:
3563fb26180SMichael Walsh        return (0, "", "") if return_stderr else (0, "")
35721083d28SMichael Walsh
3583fb26180SMichael Walsh    # Convert a string python dictionary definition to a dictionary.
3593fb26180SMichael Walsh    valid_rcs = fa.source_to_object(valid_rcs)
36021083d28SMichael Walsh    # Convert each list entry to a signed value.
3613fb26180SMichael Walsh    valid_rcs = [gm.to_signed(x) for x in valid_rcs]
36221083d28SMichael Walsh
3633fb26180SMichael Walsh    stderr = subprocess.PIPE if return_stderr else subprocess.STDOUT
36421083d28SMichael Walsh
3659e042ad3SMichael Walsh    # Write all output to func_out_history_buf rather than directly to stdout.  This allows us to decide
3669e042ad3SMichael Walsh    # what to print after all attempts to run the command string have been made.  func_out_history_buf will
3679e042ad3SMichael Walsh    # contain the complete history from the current invocation of this function.
3683fb26180SMichael Walsh    global command_timed_out
3693fb26180SMichael Walsh    command_timed_out = False
3703fb26180SMichael Walsh    func_out_history_buf = ""
37121083d28SMichael Walsh    for attempt_num in range(1, max_attempts + 1):
37220f38712SPatrick Williams        sub_proc = subprocess.Popen(
37320f38712SPatrick Williams            command_string,
37421083d28SMichael Walsh            bufsize=1,
37521083d28SMichael Walsh            shell=True,
3763fb26180SMichael Walsh            universal_newlines=True,
37720f38712SPatrick Williams            executable="/bin/bash",
378e7e9171eSGeorge Keishing            stdin=subprocess.PIPE,
37921083d28SMichael Walsh            stdout=subprocess.PIPE,
38020f38712SPatrick Williams            stderr=stderr,
38120f38712SPatrick Williams        )
382faafa9c5SMichael Walsh        if fork:
3834d5f6686SMichael Walsh            return sub_proc
3843fb26180SMichael Walsh
3853fb26180SMichael Walsh        if time_out:
38621083d28SMichael Walsh            command_timed_out = False
38721083d28SMichael Walsh            # Designate a SIGALRM handling function and set alarm.
38821083d28SMichael Walsh            signal.signal(signal.SIGALRM, shell_cmd_timed_out)
38921083d28SMichael Walsh            signal.alarm(time_out)
39021083d28SMichael Walsh        try:
3913fb26180SMichael Walsh            stdout_buf, stderr_buf = sub_proc.communicate()
39221083d28SMichael Walsh        except IOError:
39321083d28SMichael Walsh            command_timed_out = True
39421083d28SMichael Walsh        # Restore the original SIGALRM handler and clear the alarm.
39521083d28SMichael Walsh        signal.signal(signal.SIGALRM, original_sigalrm_handler)
39621083d28SMichael Walsh        signal.alarm(0)
3973fb26180SMichael Walsh
3989e042ad3SMichael Walsh        # Output from this loop iteration is written to func_out_buf for later processing.  This can include
3999e042ad3SMichael Walsh        # stdout, stderr and our own error messages.
4003fb26180SMichael Walsh        func_out_buf = ""
4013fb26180SMichael Walsh        if print_output:
4023fb26180SMichael Walsh            if return_stderr:
4033fb26180SMichael Walsh                func_out_buf += stderr_buf
4043fb26180SMichael Walsh            func_out_buf += stdout_buf
4053fb26180SMichael Walsh        shell_rc = sub_proc.returncode
4063fb26180SMichael Walsh        if shell_rc in valid_rcs:
4079e042ad3SMichael Walsh            # Check output for text indicating there is an error.
40820f38712SPatrick Williams            if error_regexes and re.match("|".join(error_regexes), stdout_buf):
4099e042ad3SMichael Walsh                shell_rc = -1
4109e042ad3SMichael Walsh            else:
41121083d28SMichael Walsh                break
41221083d28SMichael Walsh        err_msg = "The prior shell command failed.\n"
41321083d28SMichael Walsh        err_msg += gp.sprint_var(attempt_num)
4143fb26180SMichael Walsh        err_msg += gp.sprint_vars(command_string, command_timed_out, time_out)
4153fb26180SMichael Walsh        err_msg += gp.sprint_varx("child_pid", sub_proc.pid)
4163fb26180SMichael Walsh        err_msg += gp.sprint_vars(shell_rc, valid_rcs, fmt=gp.hexa())
4179e042ad3SMichael Walsh        if error_regexes:
4189e042ad3SMichael Walsh            err_msg += gp.sprint_vars(error_regexes)
41921083d28SMichael Walsh        if not print_output:
42021083d28SMichael Walsh            if return_stderr:
4213fb26180SMichael Walsh                err_msg += "stderr_buf:\n" + stderr_buf
4223fb26180SMichael Walsh            err_msg += "stdout_buf:\n" + stdout_buf
42321083d28SMichael Walsh        if show_err:
4243fb26180SMichael Walsh            func_out_buf += gp.sprint_error_report(err_msg)
42521083d28SMichael Walsh        if attempt_num < max_attempts:
4263fb26180SMichael Walsh            cmd_buf = "time.sleep(" + str(retry_sleep_time) + ")"
4273fb26180SMichael Walsh            if show_err:
4283fb26180SMichael Walsh                func_out_buf += gp.sprint_issuing(cmd_buf)
4293fb26180SMichael Walsh            exec(cmd_buf)
4303fb26180SMichael Walsh        func_out_history_buf += func_out_buf
43121083d28SMichael Walsh
4323fb26180SMichael Walsh    if shell_rc in valid_rcs:
4333fb26180SMichael Walsh        gp.gp_print(func_out_buf)
4343fb26180SMichael Walsh    else:
4353fb26180SMichael Walsh        if show_err:
43620f38712SPatrick Williams            gp.gp_print(func_out_history_buf, stream="stderr")
4373fb26180SMichael Walsh        else:
4389e042ad3SMichael Walsh            # There is no error information to show so just print output from last loop iteration.
4393fb26180SMichael Walsh            gp.gp_print(func_out_buf)
44021083d28SMichael Walsh        if not ignore_err:
4419e042ad3SMichael Walsh            # If the caller has already asked to show error info, avoid repeating that in the failure message.
44220f38712SPatrick Williams            err_msg = (
44320f38712SPatrick Williams                "The prior shell command failed.\n" if show_err else err_msg
44420f38712SPatrick Williams            )
44521083d28SMichael Walsh            if robot_env:
44621083d28SMichael Walsh                BuiltIn().fail(err_msg)
44721083d28SMichael Walsh            else:
4483fb26180SMichael Walsh                raise ValueError(err_msg)
44921083d28SMichael Walsh
45020f38712SPatrick Williams    return (
45120f38712SPatrick Williams        (shell_rc, stdout_buf, stderr_buf)
45220f38712SPatrick Williams        if return_stderr
4533fb26180SMichael Walsh        else (shell_rc, stdout_buf)
45420f38712SPatrick Williams    )
45521083d28SMichael Walsh
45621083d28SMichael Walsh
45721083d28SMichael Walshdef t_shell_cmd(command_string, **kwargs):
45821083d28SMichael Walsh    r"""
4599e042ad3SMichael Walsh    Search upward in the the call stack to obtain the test_mode argument, add it to kwargs and then call
4609e042ad3SMichael Walsh    shell_cmd and return the result.
46121083d28SMichael Walsh
46221083d28SMichael Walsh    See shell_cmd prolog for details on all arguments.
46321083d28SMichael Walsh    """
46421083d28SMichael Walsh
46520f38712SPatrick Williams    if "test_mode" in kwargs:
46620f38712SPatrick Williams        error_message = (
46720f38712SPatrick Williams            "Programmer error - test_mode is not a valid"
46820f38712SPatrick Williams            + " argument to this function."
46920f38712SPatrick Williams        )
47021083d28SMichael Walsh        gp.print_error_report(error_message)
47121083d28SMichael Walsh        exit(1)
47221083d28SMichael Walsh
47320f38712SPatrick Williams    test_mode = int(gp.get_stack_var("test_mode", 0))
47420f38712SPatrick Williams    kwargs["test_mode"] = test_mode
47521083d28SMichael Walsh
47621083d28SMichael Walsh    return shell_cmd(command_string, **kwargs)
47716244c28SGeorge Keishing
47816244c28SGeorge Keishing
4794d5f6686SMichael Walshdef kill_cmd(popen, sig=signal.SIGTERM):
4804d5f6686SMichael Walsh    r"""
4819e042ad3SMichael Walsh    Kill the subprocess represented by the Popen object and return a tuple consisting of the shell return
4829e042ad3SMichael Walsh    code and the output.
4834d5f6686SMichael Walsh
4849e042ad3SMichael Walsh    This function is meant to be used as the follow-up for a call to shell_cmd(..., fork=1).
4854d5f6686SMichael Walsh
4864d5f6686SMichael Walsh    Example:
4874d5f6686SMichael Walsh    popen = shell_cmd("some_pgm.py", fork=1)
4884d5f6686SMichael Walsh    ...
4894d5f6686SMichael Walsh    shell_rc, output = kill_cmd(popen)
4904d5f6686SMichael Walsh
4914d5f6686SMichael Walsh    Description of argument(s):
4929e042ad3SMichael Walsh    popen                           A Popen object returned by the subprocess.Popen() command.
4934d5f6686SMichael Walsh    sig                             The signal to be sent to the child process.
4944d5f6686SMichael Walsh    """
4954d5f6686SMichael Walsh
4964d5f6686SMichael Walsh    gp.dprint_var(popen.pid)
4974d5f6686SMichael Walsh    os.killpg(popen.pid, sig)
4984d5f6686SMichael Walsh    stdout, stderr = popen.communicate()
4994d5f6686SMichael Walsh    shell_rc = popen.returncode
5004d5f6686SMichael Walsh    return (shell_rc, stdout, stderr) if stderr else (shell_rc, stdout)
5014d5f6686SMichael Walsh
5024d5f6686SMichael Walsh
50316244c28SGeorge Keishingdef re_order_kwargs(stack_frame_ix, **kwargs):
50416244c28SGeorge Keishing    r"""
5059e042ad3SMichael Walsh    Re-order the kwargs to match the order in which they were specified on a function invocation and return
5069e042ad3SMichael Walsh    as an ordered dictionary.
50716244c28SGeorge Keishing
5089e042ad3SMichael Walsh    Note that this re_order_kwargs function should not be necessary in python versions 3.6 and beyond.
50916244c28SGeorge Keishing
51016244c28SGeorge Keishing    Example:
51116244c28SGeorge Keishing
51216244c28SGeorge Keishing    The caller calls func1 like this:
51316244c28SGeorge Keishing
51416244c28SGeorge Keishing    func1('mike', arg1='one', arg2='two', arg3='three')
51516244c28SGeorge Keishing
51616244c28SGeorge Keishing    And func1 is defined as follows:
51716244c28SGeorge Keishing
51816244c28SGeorge Keishing    def func1(first_arg, **kwargs):
51916244c28SGeorge Keishing
52016244c28SGeorge Keishing        kwargs = re_order_kwargs(first_arg_num=2, stack_frame_ix=3, **kwargs)
52116244c28SGeorge Keishing
5229e042ad3SMichael Walsh    The kwargs dictionary before calling re_order_kwargs (where order is not guaranteed):
52316244c28SGeorge Keishing
52416244c28SGeorge Keishing    kwargs:
52516244c28SGeorge Keishing      kwargs[arg3]:          three
52616244c28SGeorge Keishing      kwargs[arg2]:          two
52716244c28SGeorge Keishing      kwargs[arg1]:          one
52816244c28SGeorge Keishing
52916244c28SGeorge Keishing    The kwargs dictionary after calling re_order_kwargs:
53016244c28SGeorge Keishing
53116244c28SGeorge Keishing    kwargs:
53216244c28SGeorge Keishing      kwargs[arg1]:          one
53316244c28SGeorge Keishing      kwargs[arg2]:          two
53416244c28SGeorge Keishing      kwargs[arg3]:          three
53516244c28SGeorge Keishing
5369e042ad3SMichael Walsh    Note that the re-ordered kwargs match the order specified on the call to func1.
53716244c28SGeorge Keishing
53816244c28SGeorge Keishing    Description of argument(s):
5399e042ad3SMichael Walsh    stack_frame_ix                  The stack frame of the function whose kwargs values must be re-ordered.
5409e042ad3SMichael Walsh                                    0 is the stack frame of re_order_kwargs, 1 is the stack from of its
5419e042ad3SMichael Walsh                                    caller and so on.
5429e042ad3SMichael Walsh    kwargs                          The keyword argument dictionary which is to be re-ordered.
54316244c28SGeorge Keishing    """
54416244c28SGeorge Keishing
54516244c28SGeorge Keishing    new_kwargs = collections.OrderedDict()
54616244c28SGeorge Keishing
54716244c28SGeorge Keishing    # Get position number of first keyword on the calling line of code.
54820f38712SPatrick Williams    (args, varargs, keywords, locals) = inspect.getargvalues(
54920f38712SPatrick Williams        inspect.stack()[stack_frame_ix][0]
55020f38712SPatrick Williams    )
55116244c28SGeorge Keishing    first_kwarg_pos = 1 + len(args)
55216244c28SGeorge Keishing    if varargs is not None:
55316244c28SGeorge Keishing        first_kwarg_pos += len(locals[varargs])
55416244c28SGeorge Keishing    for arg_num in range(first_kwarg_pos, first_kwarg_pos + len(kwargs)):
55516244c28SGeorge Keishing        # This will result in an arg_name value such as "arg1='one'".
55616244c28SGeorge Keishing        arg_name = gp.get_arg_name(None, arg_num, stack_frame_ix + 2)
55716244c28SGeorge Keishing        # Continuing with the prior example, the following line will result
55816244c28SGeorge Keishing        # in key being set to 'arg1'.
55920f38712SPatrick Williams        key = arg_name.split("=")[0]
56016244c28SGeorge Keishing        new_kwargs[key] = kwargs[key]
56116244c28SGeorge Keishing
56216244c28SGeorge Keishing    return new_kwargs
56316244c28SGeorge Keishing
56416244c28SGeorge Keishing
56516244c28SGeorge Keishingdef default_arg_delim(arg_dashes):
56616244c28SGeorge Keishing    r"""
56716244c28SGeorge Keishing    Return the default argument delimiter value for the given arg_dashes value.
56816244c28SGeorge Keishing
5699e042ad3SMichael Walsh    Note: this function is useful for functions that manipulate bash command line arguments (e.g. --parm=1 or
5709e042ad3SMichael Walsh    -parm 1).
57116244c28SGeorge Keishing
57216244c28SGeorge Keishing    Description of argument(s):
5739e042ad3SMichael Walsh    arg_dashes                      The argument dashes specifier (usually, "-" or "--").
57416244c28SGeorge Keishing    """
57516244c28SGeorge Keishing
57616244c28SGeorge Keishing    if arg_dashes == "--":
57716244c28SGeorge Keishing        return "="
57816244c28SGeorge Keishing
57916244c28SGeorge Keishing    return " "
58016244c28SGeorge Keishing
58116244c28SGeorge Keishing
58216244c28SGeorge Keishingdef create_command_string(command, *pos_parms, **options):
58316244c28SGeorge Keishing    r"""
5849e042ad3SMichael Walsh    Create and return a bash command string consisting of the given arguments formatted as text.
58516244c28SGeorge Keishing
58616244c28SGeorge Keishing    The default formatting of options is as follows:
58716244c28SGeorge Keishing
58816244c28SGeorge Keishing    <single dash><option name><space delim><option value>
58916244c28SGeorge Keishing
59016244c28SGeorge Keishing    Example:
59116244c28SGeorge Keishing
59216244c28SGeorge Keishing    -parm value
59316244c28SGeorge Keishing
5949e042ad3SMichael Walsh    The caller can change the kind of dashes/delimiters used by specifying "arg_dashes" and/or "arg_delims"
5959e042ad3SMichael Walsh    as options.  These options are processed specially by the create_command_string function and do NOT get
5969e042ad3SMichael Walsh    inserted into the resulting command string.  All options following the arg_dashes/arg_delims options will
5979e042ad3SMichael Walsh    then use the specified values for dashes/delims.  In the special case of arg_dashes equal to "--", the
59816244c28SGeorge Keishing    arg_delim will automatically be changed to "=".  See examples below.
59916244c28SGeorge Keishing
60016244c28SGeorge Keishing    Quoting rules:
60116244c28SGeorge Keishing
6029e042ad3SMichael Walsh    The create_command_string function will single quote option values as needed to prevent bash expansion.
6039e042ad3SMichael Walsh    If the caller wishes to defeat this action, they may single or double quote the option value themselves.
6049e042ad3SMichael Walsh    See examples below.
60516244c28SGeorge Keishing
6069e042ad3SMichael Walsh    pos_parms are NOT automatically quoted.  The caller is advised to either explicitly add quotes or to use
6079e042ad3SMichael Walsh    the quote_bash_parm functions to quote any pos_parms.
60816244c28SGeorge Keishing
60916244c28SGeorge Keishing    Examples:
61016244c28SGeorge Keishing
61116244c28SGeorge Keishing    command_string = create_command_string('cd', '~')
61216244c28SGeorge Keishing
61316244c28SGeorge Keishing    Result:
61416244c28SGeorge Keishing    cd ~
61516244c28SGeorge Keishing
6169e042ad3SMichael Walsh    Note that the pos_parm ("~") does NOT get quoted, as per the aforementioned rules.  If quotes are
6179e042ad3SMichael Walsh    desired, they may be added explicitly by the caller:
61816244c28SGeorge Keishing
61916244c28SGeorge Keishing    command_string = create_command_string('cd', '\'~\'')
62016244c28SGeorge Keishing
62116244c28SGeorge Keishing    Result:
62216244c28SGeorge Keishing    cd '~'
62316244c28SGeorge Keishing
62416244c28SGeorge Keishing    command_string = create_command_string('grep', '\'^[^ ]*=\'',
62516244c28SGeorge Keishing        '/tmp/myfile', i=None, m='1', arg_dashes='--', color='always')
62616244c28SGeorge Keishing
62716244c28SGeorge Keishing    Result:
62816244c28SGeorge Keishing    grep -i -m 1 --color=always '^[^ ]*=' /tmp/myfile
62916244c28SGeorge Keishing
6309e042ad3SMichael Walsh    In the preceding example, note the use of None to cause the "i" parm to be treated as a flag (i.e. no
6319e042ad3SMichael Walsh    argument value is generated).  Also, note the use of arg_dashes to change the type of dashes used on all
6329e042ad3SMichael Walsh    subsequent options.  The following example is equivalent to the prior.  Note that quote_bash_parm is used
6339e042ad3SMichael Walsh    instead of including the quotes explicitly.
63416244c28SGeorge Keishing
63516244c28SGeorge Keishing    command_string = create_command_string('grep', quote_bash_parm('^[^ ]*='),
63616244c28SGeorge Keishing        '/tmp/myfile', i=None,  m='1', arg_dashes='--', color='always')
63716244c28SGeorge Keishing
63816244c28SGeorge Keishing    Result:
63916244c28SGeorge Keishing    grep -i -m 1 --color=always '^[^ ]*=' /tmp/myfile
64016244c28SGeorge Keishing
6419e042ad3SMichael Walsh    In the following example, note the automatic quoting of the password option, as per the aforementioned
6429e042ad3SMichael Walsh    rules.
64316244c28SGeorge Keishing
64416244c28SGeorge Keishing    command_string = create_command_string('my_pgm', '/tmp/myfile', i=None,
64516244c28SGeorge Keishing        m='1', arg_dashes='--', password='${my_pw}')
64616244c28SGeorge Keishing
6479e042ad3SMichael Walsh    However, let's say that the caller wishes to have bash expand the password value.  To achieve this, the
6489e042ad3SMichael Walsh    caller can use double quotes:
64916244c28SGeorge Keishing
65016244c28SGeorge Keishing    command_string = create_command_string('my_pgm', '/tmp/myfile', i=None,
65116244c28SGeorge Keishing        m='1', arg_dashes='--', password='"${my_pw}"')
65216244c28SGeorge Keishing
65316244c28SGeorge Keishing    Result:
65416244c28SGeorge Keishing    my_pgm -i -m 1 --password="${my_pw}" /tmp/myfile
65516244c28SGeorge Keishing
65616244c28SGeorge Keishing    command_string = create_command_string('ipmitool', 'power status',
657*7ac1f56fSGeorge Keishing        I='lanplus', C='3', 'p=623', U='root', P='********', H='xx.xx.xx.xx')
65816244c28SGeorge Keishing
65916244c28SGeorge Keishing    Result:
660*7ac1f56fSGeorge Keishing    ipmitool -I lanplus -C 3 -p 623 -U root -P ********* -H xx.xx.xx.xx power status
66116244c28SGeorge Keishing
6629e042ad3SMichael Walsh    By default create_command_string will take measures to preserve the order of the callers options.  In
6639e042ad3SMichael Walsh    some cases, this effort may fail (as when calling directly from a robot program).  In this case, the
6649e042ad3SMichael Walsh    caller can accept the responsibility of keeping an ordered list of options by calling this function with
6659e042ad3SMichael Walsh    the last positional parm as some kind of dictionary (preferably an OrderedDict) and avoiding the use of
6669e042ad3SMichael Walsh    any actual option args.
66716244c28SGeorge Keishing
66816244c28SGeorge Keishing    Example:
66916244c28SGeorge Keishing    kwargs = collections.OrderedDict([('pass', 0), ('fail', 0)])
67016244c28SGeorge Keishing    command_string = create_command_string('my program', 'pos_parm1', kwargs)
67116244c28SGeorge Keishing
67216244c28SGeorge Keishing    Result:
67316244c28SGeorge Keishing
67416244c28SGeorge Keishing    my program -pass 0 -fail 0 pos_parm1
67516244c28SGeorge Keishing
6769e042ad3SMichael Walsh    Note to programmers who wish to write a wrapper to this function:  If the python version is less than
6779e042ad3SMichael Walsh    3.6, to get the options to be processed correctly, the wrapper function must include a _stack_frame_ix_
6789e042ad3SMichael Walsh    keyword argument to allow this function to properly re-order options:
67916244c28SGeorge Keishing
68016244c28SGeorge Keishing    def create_ipmi_ext_command_string(command, **kwargs):
68116244c28SGeorge Keishing
68216244c28SGeorge Keishing        return create_command_string('ipmitool', command, _stack_frame_ix_=2,
68316244c28SGeorge Keishing            **kwargs)
68416244c28SGeorge Keishing
68516244c28SGeorge Keishing    Example call of wrapper function:
68616244c28SGeorge Keishing
6879e042ad3SMichael Walsh    command_string = create_ipmi_ext_command_string('power status', I='lanplus')
68816244c28SGeorge Keishing
68916244c28SGeorge Keishing    Description of argument(s):
6909e042ad3SMichael Walsh    command                         The command (e.g. "cat", "sort", "ipmitool", etc.).
6919e042ad3SMichael Walsh    pos_parms                       The positional parms for the command (e.g. PATTERN, FILENAME, etc.).
6929e042ad3SMichael Walsh                                    These will be placed at the end of the resulting command string.
6939e042ad3SMichael Walsh    options                         The command options (e.g. "-m 1", "--max-count=NUM", etc.).  Note that if
6949e042ad3SMichael Walsh                                    the value of any option is None, then it will be understood to be a flag
6959e042ad3SMichael Walsh                                    (for which no value is required).
69616244c28SGeorge Keishing    """
69716244c28SGeorge Keishing
69816244c28SGeorge Keishing    arg_dashes = "-"
69916244c28SGeorge Keishing    delim = default_arg_delim(arg_dashes)
70016244c28SGeorge Keishing
70116244c28SGeorge Keishing    command_string = command
70216244c28SGeorge Keishing
70323ac9b4cSMichael Walsh    if len(pos_parms) > 0 and gp.is_dict(pos_parms[-1]):
70416244c28SGeorge Keishing        # Convert pos_parms from tuple to list.
70516244c28SGeorge Keishing        pos_parms = list(pos_parms)
7069e042ad3SMichael Walsh        # Re-assign options to be the last pos_parm value (which is a dictionary).
70716244c28SGeorge Keishing        options = pos_parms[-1]
70816244c28SGeorge Keishing        # Now delete the last pos_parm.
70916244c28SGeorge Keishing        del pos_parms[-1]
71016244c28SGeorge Keishing    else:
7119e042ad3SMichael Walsh        # Either get stack_frame_ix from the caller via options or set it to the default value.
71220f38712SPatrick Williams        stack_frame_ix = options.pop("_stack_frame_ix_", 1)
713fffddca1SMichael Walsh        if gm.python_version < gm.ordered_dict_version:
7149e042ad3SMichael Walsh            # Re-establish the original options order as specified on the original line of code.  This
7159e042ad3SMichael Walsh            # function depends on correct order.
71616244c28SGeorge Keishing            options = re_order_kwargs(stack_frame_ix, **options)
71716244c28SGeorge Keishing    for key, value in options.items():
71816244c28SGeorge Keishing        # Check for special values in options and process them.
71916244c28SGeorge Keishing        if key == "arg_dashes":
72016244c28SGeorge Keishing            arg_dashes = str(value)
72116244c28SGeorge Keishing            delim = default_arg_delim(arg_dashes)
72216244c28SGeorge Keishing            continue
72316244c28SGeorge Keishing        if key == "arg_delim":
72416244c28SGeorge Keishing            delim = str(value)
72516244c28SGeorge Keishing            continue
72616244c28SGeorge Keishing        # Format the options elements into the command string.
72716244c28SGeorge Keishing        command_string += " " + arg_dashes + key
72816244c28SGeorge Keishing        if value is not None:
72916244c28SGeorge Keishing            command_string += delim
73016244c28SGeorge Keishing            if re.match(r'^(["].*["]|[\'].*[\'])$', str(value)):
73116244c28SGeorge Keishing                # Already quoted.
73216244c28SGeorge Keishing                command_string += str(value)
73316244c28SGeorge Keishing            else:
73416244c28SGeorge Keishing                command_string += gm.quote_bash_parm(str(value))
7359e042ad3SMichael Walsh    # Finally, append the pos_parms to the end of the command_string.  Use filter to eliminate blank pos
7369e042ad3SMichael Walsh    # parms.
73720f38712SPatrick Williams    command_string = " ".join([command_string] + list(filter(None, pos_parms)))
73816244c28SGeorge Keishing
73916244c28SGeorge Keishing    return command_string
740