1#!/usr/bin/env python
2
3r"""
4This module provides command execution functions such as cmd_fnc and cmd_fnc_u.
5"""
6
7import os
8import sys
9import subprocess
10import collections
11import signal
12import time
13import re
14import inspect
15
16import gen_print as gp
17import gen_valid as gv
18import gen_misc as gm
19import func_args as fa
20
21robot_env = gp.robot_env
22
23if robot_env:
24    from robot.libraries.BuiltIn import BuiltIn
25
26
27# cmd_fnc and cmd_fnc_u should now be considered deprecated.  shell_cmd and
28# t_shell_cmd should be used instead.
29def cmd_fnc(cmd_buf,
30            quiet=None,
31            test_mode=None,
32            debug=0,
33            print_output=1,
34            show_err=1,
35            return_stderr=0,
36            ignore_err=1):
37    r"""
38    Run the given command in a shell and return the shell return code and the
39    output.
40
41    Description of arguments:
42    cmd_buf                         The command string to be run in a shell.
43    quiet                           Indicates whether this function should run
44                                    the print_issuing() function which prints
45                                    "Issuing: <cmd string>" to stdout.
46    test_mode                       If test_mode is set, this function will
47                                    not actually run the command.  If
48                                    print_output is set, it will print
49                                    "(test_mode) Issuing: <cmd string>" to
50                                    stdout.
51    debug                           If debug is set, this function will print
52                                    extra debug info.
53    print_output                    If this is set, this function will print
54                                    the stdout/stderr generated by the shell
55                                    command.
56    show_err                        If show_err is set, this function will
57                                    print a standardized error report if the
58                                    shell command returns non-zero.
59    return_stderr                   If return_stderr is set, this function
60                                    will process the stdout and stderr streams
61                                    from the shell command separately.  It
62                                    will also return stderr in addition to the
63                                    return code and the stdout.
64    """
65
66    # Determine default values.
67    quiet = int(gm.global_default(quiet, 0))
68    test_mode = int(gm.global_default(test_mode, 0))
69
70    if debug:
71        gp.print_vars(cmd_buf, quiet, test_mode, debug)
72
73    err_msg = gv.valid_value(cmd_buf)
74    if err_msg != "":
75        raise ValueError(err_msg)
76
77    if not quiet:
78        gp.pissuing(cmd_buf, test_mode)
79
80    if test_mode:
81        if return_stderr:
82            return 0, "", ""
83        else:
84            return 0, ""
85
86    if return_stderr:
87        err_buf = ""
88        stderr = subprocess.PIPE
89    else:
90        stderr = subprocess.STDOUT
91
92    sub_proc = subprocess.Popen(cmd_buf,
93                                bufsize=1,
94                                shell=True,
95                                executable='/bin/bash',
96                                stdout=subprocess.PIPE,
97                                stderr=stderr)
98    out_buf = ""
99    if return_stderr:
100        for line in sub_proc.stderr:
101            try:
102                err_buf += line
103            except TypeError:
104                line = line.decode("utf-8")
105                err_buf += line
106            if not print_output:
107                continue
108            gp.gp_print(line)
109    for line in sub_proc.stdout:
110        try:
111            out_buf += line
112        except TypeError:
113            line = line.decode("utf-8")
114            out_buf += line
115        if not print_output:
116            continue
117        gp.gp_print(line)
118    if print_output and not robot_env:
119        sys.stdout.flush()
120    sub_proc.communicate()
121    shell_rc = sub_proc.returncode
122    if shell_rc != 0:
123        err_msg = "The prior shell command failed.\n"
124        err_msg += gp.sprint_var(shell_rc, gp.hexa())
125        if not print_output:
126            err_msg += "out_buf:\n" + out_buf
127
128        if show_err:
129            gp.print_error_report(err_msg)
130        if not ignore_err:
131            if robot_env:
132                BuiltIn().fail(err_msg)
133            else:
134                raise ValueError(err_msg)
135
136    if return_stderr:
137        return shell_rc, out_buf, err_buf
138    else:
139        return shell_rc, out_buf
140
141
142def cmd_fnc_u(cmd_buf,
143              quiet=None,
144              debug=None,
145              print_output=1,
146              show_err=1,
147              return_stderr=0,
148              ignore_err=1):
149    r"""
150    Call cmd_fnc with test_mode=0.  See cmd_fnc (above) for details.
151
152    Note the "u" in "cmd_fnc_u" stands for "unconditional".
153    """
154
155    return cmd_fnc(cmd_buf, test_mode=0, quiet=quiet, debug=debug,
156                   print_output=print_output, show_err=show_err,
157                   return_stderr=return_stderr, ignore_err=ignore_err)
158
159
160def parse_command_string(command_string):
161    r"""
162    Parse a bash command-line command string and return the result as a
163    dictionary of parms.
164
165    This can be useful for answering questions like "What did the user specify
166    as the value for parm x in the command string?".
167
168    This function expects the command string to follow the following posix
169    conventions:
170    - Short parameters:
171      -<parm name><space><arg value>
172    - Long parameters:
173      --<parm name>=<arg value>
174
175    The first item in the string will be considered to be the command.  All
176    values not conforming to the specifications above will be considered
177    positional parms.  If there are multiple parms with the same name, they
178    will be put into a list (see illustration below where "-v" is specified
179    multiple times).
180
181    Description of argument(s):
182    command_string                  The complete command string including all
183                                    parameters and arguments.
184
185    Sample input:
186
187    robot_cmd_buf:                                    robot -v
188    OPENBMC_HOST:dummy1 -v keyword_string:'Set Auto Reboot  no' -v
189    lib_file_path:/home/user1/git/openbmc-test-automation/lib/utils.robot -v
190    quiet:0 -v test_mode:0 -v debug:0
191    --outputdir='/home/user1/status/children/'
192    --output=dummy1.Auto_reboot.170802.124544.output.xml
193    --log=dummy1.Auto_reboot.170802.124544.log.html
194    --report=dummy1.Auto_reboot.170802.124544.report.html
195    /home/user1/git/openbmc-test-automation/extended/run_keyword.robot
196
197    Sample output:
198
199    robot_cmd_buf_dict:
200      robot_cmd_buf_dict[command]:                    robot
201      robot_cmd_buf_dict[v]:
202        robot_cmd_buf_dict[v][0]:                     OPENBMC_HOST:dummy1
203        robot_cmd_buf_dict[v][1]:                     keyword_string:Set Auto
204        Reboot no
205        robot_cmd_buf_dict[v][2]:
206        lib_file_path:/home/user1/git/openbmc-test-automation/lib/utils.robot
207        robot_cmd_buf_dict[v][3]:                     quiet:0
208        robot_cmd_buf_dict[v][4]:                     test_mode:0
209        robot_cmd_buf_dict[v][5]:                     debug:0
210      robot_cmd_buf_dict[outputdir]:
211      /home/user1/status/children/
212      robot_cmd_buf_dict[output]:
213      dummy1.Auto_reboot.170802.124544.output.xml
214      robot_cmd_buf_dict[log]:
215      dummy1.Auto_reboot.170802.124544.log.html
216      robot_cmd_buf_dict[report]:
217      dummy1.Auto_reboot.170802.124544.report.html
218      robot_cmd_buf_dict[positional]:
219      /home/user1/git/openbmc-test-automation/extended/run_keyword.robot
220    """
221
222    # We want the parms in the string broken down the way bash would do it,
223    # so we'll call upon bash to do that by creating a simple inline bash
224    # function.
225    bash_func_def = "function parse { for parm in \"${@}\" ; do" +\
226        " echo $parm ; done ; }"
227
228    rc, outbuf = cmd_fnc_u(bash_func_def + " ; parse " + command_string,
229                           quiet=1, print_output=0)
230    command_string_list = outbuf.rstrip("\n").split("\n")
231
232    command_string_dict = collections.OrderedDict()
233    ix = 1
234    command_string_dict['command'] = command_string_list[0]
235    while ix < len(command_string_list):
236        if command_string_list[ix].startswith("--"):
237            key, value = command_string_list[ix].split("=")
238            key = key.lstrip("-")
239        elif command_string_list[ix].startswith("-"):
240            key = command_string_list[ix].lstrip("-")
241            ix += 1
242            try:
243                value = command_string_list[ix]
244            except IndexError:
245                value = ""
246        else:
247            key = 'positional'
248            value = command_string_list[ix]
249        if key in command_string_dict:
250            if isinstance(command_string_dict[key], str):
251                command_string_dict[key] = [command_string_dict[key]]
252            command_string_dict[key].append(value)
253        else:
254            command_string_dict[key] = value
255        ix += 1
256
257    return command_string_dict
258
259
260# Save the original SIGALRM handler for later restoration by shell_cmd.
261original_sigalrm_handler = signal.getsignal(signal.SIGALRM)
262
263
264def shell_cmd_timed_out(signal_number,
265                        frame):
266    r"""
267    Handle an alarm signal generated during the shell_cmd function.
268    """
269
270    gp.dprint_executing()
271    global command_timed_out
272    command_timed_out = True
273    # Get subprocess pid from shell_cmd's call stack.
274    sub_proc = gp.get_stack_var('sub_proc', 0)
275    pid = sub_proc.pid
276    gp.dprint_var(pid)
277    # Terminate the child process group.
278    os.killpg(pid, signal.SIGKILL)
279    # Restore the original SIGALRM handler.
280    signal.signal(signal.SIGALRM, original_sigalrm_handler)
281
282    return
283
284
285def shell_cmd(command_string,
286              quiet=None,
287              print_output=None,
288              show_err=1,
289              test_mode=0,
290              time_out=None,
291              max_attempts=1,
292              retry_sleep_time=5,
293              valid_rcs=[0],
294              ignore_err=None,
295              return_stderr=0,
296              fork=0):
297    r"""
298    Run the given command string in a shell and return a tuple consisting of
299    the shell return code and the output.
300
301    Description of argument(s):
302    command_string                  The command string to be run in a shell
303                                    (e.g. "ls /tmp").
304    quiet                           If set to 0, this function will print
305                                    "Issuing: <cmd string>" to stdout.  When
306                                    the quiet argument is set to None, this
307                                    function will assign a default value by
308                                    searching upward in the stack for the
309                                    quiet variable value.  If no such value is
310                                    found, quiet is set to 0.
311    print_output                    If this is set, this function will print
312                                    the stdout/stderr generated by the shell
313                                    command to stdout.
314    show_err                        If show_err is set, this function will
315                                    print a standardized error report if the
316                                    shell command fails (i.e. if the shell
317                                    command returns a shell_rc that is not in
318                                    valid_rcs).  Note: Error text is only
319                                    printed if ALL attempts to run the
320                                    command_string fail.  In other words, if
321                                    the command execution is ultimately
322                                    successful, initial failures are hidden.
323    test_mode                       If test_mode is set, this function will
324                                    not actually run the command.  If
325                                    print_output is also set, this function
326                                    will print "(test_mode) Issuing: <cmd
327                                    string>" to stdout.  A caller should call
328                                    shell_cmd directly if they wish to have
329                                    the command string run unconditionally.
330                                    They should call the t_shell_cmd wrapper
331                                    (defined below) if they wish to run the
332                                    command string only if the prevailing
333                                    test_mode variable is set to 0.
334    time_out                        A time-out value expressed in seconds.  If
335                                    the command string has not finished
336                                    executing within <time_out> seconds, it
337                                    will be halted and counted as an error.
338    max_attempts                    The max number of attempts that should be
339                                    made to run the command string.
340    retry_sleep_time                The number of seconds to sleep between
341                                    attempts.
342    valid_rcs                       A list of integers indicating which
343                                    shell_rc values are not to be considered
344                                    errors.
345    ignore_err                      Ignore error means that a failure
346                                    encountered by running the command string
347                                    will not be raised as a python exception.
348                                    When the ignore_err argument is set to
349                                    None, this function will assign a default
350                                    value by searching upward in the stack for
351                                    the ignore_err variable value.  If no such
352                                    value is found, ignore_err is set to 1.
353    return_stderr                   If return_stderr is set, this function
354                                    will process the stdout and stderr streams
355                                    from the shell command separately.  In
356                                    such a case, the tuple returned by this
357                                    function will consist of three values
358                                    rather than just two: rc, stdout, stderr.
359    fork                            Run the command string asynchronously
360                                    (i.e. don't wait for status of the child
361                                    process and don't try to get
362                                    stdout/stderr).
363    """
364
365    err_msg = gv.valid_value(command_string)
366    if err_msg:
367        raise ValueError(err_msg)
368
369    # Assign default values to some of the arguments to this function.
370    quiet = int(gm.dft(quiet, gp.get_stack_var('quiet', 0)))
371    print_output = int(gm.dft(print_output, not quiet))
372    show_err = int(show_err)
373    ignore_err = int(gm.dft(ignore_err, gp.get_stack_var('ignore_err', 1)))
374
375    gp.qprint_issuing(command_string, test_mode)
376    if test_mode:
377        return (0, "", "") if return_stderr else (0, "")
378
379    # Convert a string python dictionary definition to a dictionary.
380    valid_rcs = fa.source_to_object(valid_rcs)
381    # Convert each list entry to a signed value.
382    valid_rcs = [gm.to_signed(x) for x in valid_rcs]
383
384    stderr = subprocess.PIPE if return_stderr else subprocess.STDOUT
385
386    # Write all output to func_out_history_buf rather than directly to
387    # stdout.  This allows us to decide what to print after all attempts to
388    # run the command string have been made.  func_out_history_buf will
389    # contain the complete history from the current invocation of this
390    # function.
391    global command_timed_out
392    command_timed_out = False
393    func_out_history_buf = ""
394    for attempt_num in range(1, max_attempts + 1):
395        sub_proc = subprocess.Popen(command_string,
396                                    preexec_fn=os.setsid,
397                                    bufsize=1,
398                                    shell=True,
399                                    universal_newlines=True,
400                                    executable='/bin/bash',
401                                    stdout=subprocess.PIPE,
402                                    stderr=stderr)
403        if fork:
404            return (0, "", "") if return_stderr else (0, "")
405
406        if time_out:
407            command_timed_out = False
408            # Designate a SIGALRM handling function and set alarm.
409            signal.signal(signal.SIGALRM, shell_cmd_timed_out)
410            signal.alarm(time_out)
411        try:
412            stdout_buf, stderr_buf = sub_proc.communicate()
413        except IOError:
414            command_timed_out = True
415        # Restore the original SIGALRM handler and clear the alarm.
416        signal.signal(signal.SIGALRM, original_sigalrm_handler)
417        signal.alarm(0)
418
419        # Output from this loop iteration is written to func_out_buf for
420        # later processing.  This can include stdout, stderr and our own error
421        # messages.
422        func_out_buf = ""
423        if print_output:
424            if return_stderr:
425                func_out_buf += stderr_buf
426            func_out_buf += stdout_buf
427        shell_rc = sub_proc.returncode
428        if shell_rc in valid_rcs:
429            break
430        err_msg = "The prior shell command failed.\n"
431        err_msg += gp.sprint_var(attempt_num)
432        err_msg += gp.sprint_vars(command_string, command_timed_out, time_out)
433        err_msg += gp.sprint_varx("child_pid", sub_proc.pid)
434        err_msg += gp.sprint_vars(shell_rc, valid_rcs, fmt=gp.hexa())
435        if not print_output:
436            if return_stderr:
437                err_msg += "stderr_buf:\n" + stderr_buf
438            err_msg += "stdout_buf:\n" + stdout_buf
439        if show_err:
440            func_out_buf += gp.sprint_error_report(err_msg)
441        if attempt_num < max_attempts:
442            cmd_buf = "time.sleep(" + str(retry_sleep_time) + ")"
443            if show_err:
444                func_out_buf += gp.sprint_issuing(cmd_buf)
445            exec(cmd_buf)
446        func_out_history_buf += func_out_buf
447
448    if shell_rc in valid_rcs:
449        gp.gp_print(func_out_buf)
450    else:
451        if show_err:
452            gp.gp_print(func_out_history_buf, stream='stderr')
453        else:
454            # There is no error information to show so just print output from
455            # last loop iteration.
456            gp.gp_print(func_out_buf)
457        if not ignore_err:
458            # If the caller has already asked to show error info, avoid
459            # repeating that in the failure message.
460            err_msg = "The prior shell command failed.\n" if show_err \
461                else err_msg
462            if robot_env:
463                BuiltIn().fail(err_msg)
464            else:
465                raise ValueError(err_msg)
466
467    return (shell_rc, stdout_buf, stderr_buf) if return_stderr \
468        else (shell_rc, stdout_buf)
469
470
471def t_shell_cmd(command_string, **kwargs):
472    r"""
473    Search upward in the the call stack to obtain the test_mode argument, add
474    it to kwargs and then call shell_cmd and return the result.
475
476    See shell_cmd prolog for details on all arguments.
477    """
478
479    if 'test_mode' in kwargs:
480        error_message = "Programmer error - test_mode is not a valid" +\
481            " argument to this function."
482        gp.print_error_report(error_message)
483        exit(1)
484
485    test_mode = int(gp.get_stack_var('test_mode', 0))
486    kwargs['test_mode'] = test_mode
487
488    return shell_cmd(command_string, **kwargs)
489
490
491def re_order_kwargs(stack_frame_ix, **kwargs):
492    r"""
493    Re-order the kwargs to match the order in which they were specified on a
494    function invocation and return as an ordered dictionary.
495
496    Note that this re_order_kwargs function should not be necessary in python
497    versions 3.6 and beyond.
498
499    Example:
500
501    The caller calls func1 like this:
502
503    func1('mike', arg1='one', arg2='two', arg3='three')
504
505    And func1 is defined as follows:
506
507    def func1(first_arg, **kwargs):
508
509        kwargs = re_order_kwargs(first_arg_num=2, stack_frame_ix=3, **kwargs)
510
511    The kwargs dictionary before calling re_order_kwargs (where order is not
512    guaranteed):
513
514    kwargs:
515      kwargs[arg3]:          three
516      kwargs[arg2]:          two
517      kwargs[arg1]:          one
518
519    The kwargs dictionary after calling re_order_kwargs:
520
521    kwargs:
522      kwargs[arg1]:          one
523      kwargs[arg2]:          two
524      kwargs[arg3]:          three
525
526    Note that the re-ordered kwargs match the order specified on the call to
527    func1.
528
529    Description of argument(s):
530    stack_frame_ix                  The stack frame of the function whose
531                                    kwargs values must be re-ordered.  0 is
532                                    the stack frame of re_order_kwargs, 1 is
533                                    the stack from of its caller and so on.
534    kwargs                          The keyword argument dictionary which is
535                                    to be re-ordered.
536    """
537
538    new_kwargs = collections.OrderedDict()
539
540    # Get position number of first keyword on the calling line of code.
541    (args, varargs, keywords, locals) =\
542        inspect.getargvalues(inspect.stack()[stack_frame_ix][0])
543    first_kwarg_pos = 1 + len(args)
544    if varargs is not None:
545        first_kwarg_pos += len(locals[varargs])
546    for arg_num in range(first_kwarg_pos, first_kwarg_pos + len(kwargs)):
547        # This will result in an arg_name value such as "arg1='one'".
548        arg_name = gp.get_arg_name(None, arg_num, stack_frame_ix + 2)
549        # Continuing with the prior example, the following line will result
550        # in key being set to 'arg1'.
551        key = arg_name.split('=')[0]
552        new_kwargs[key] = kwargs[key]
553
554    return new_kwargs
555
556
557def default_arg_delim(arg_dashes):
558    r"""
559    Return the default argument delimiter value for the given arg_dashes value.
560
561    Note: this function is useful for functions that manipulate bash command
562    line arguments (e.g. --parm=1 or -parm 1).
563
564    Description of argument(s):
565    arg_dashes                      The argument dashes specifier (usually,
566                                    "-" or "--").
567    """
568
569    if arg_dashes == "--":
570        return "="
571
572    return " "
573
574
575def create_command_string(command, *pos_parms, **options):
576    r"""
577    Create and return a bash command string consisting of the given arguments
578    formatted as text.
579
580    The default formatting of options is as follows:
581
582    <single dash><option name><space delim><option value>
583
584    Example:
585
586    -parm value
587
588    The caller can change the kind of dashes/delimiters used by specifying
589    "arg_dashes" and/or "arg_delims" as options.  These options are processed
590    specially by the create_command_string function and do NOT get inserted
591    into the resulting command string.  All options following the
592    arg_dashes/arg_delims options will then use the specified values for
593    dashes/delims.  In the special case of arg_dashes equal to "--", the
594    arg_delim will automatically be changed to "=".  See examples below.
595
596    Quoting rules:
597
598    The create_command_string function will single quote option values as
599    needed to prevent bash expansion.  If the caller wishes to defeat this
600    action, they may single or double quote the option value themselves.  See
601    examples below.
602
603    pos_parms are NOT automatically quoted.  The caller is advised to either
604    explicitly add quotes or to use the quote_bash_parm functions to quote any
605    pos_parms.
606
607    Examples:
608
609    command_string = create_command_string('cd', '~')
610
611    Result:
612    cd ~
613
614    Note that the pos_parm ("~") does NOT get quoted, as per the
615    aforementioned rules.  If quotes are desired, they may be added explicitly
616    by the caller:
617
618    command_string = create_command_string('cd', '\'~\'')
619
620    Result:
621    cd '~'
622
623    command_string = create_command_string('grep', '\'^[^ ]*=\'',
624        '/tmp/myfile', i=None, m='1', arg_dashes='--', color='always')
625
626    Result:
627    grep -i -m 1 --color=always '^[^ ]*=' /tmp/myfile
628
629    In the preceding example, note the use of None to cause the "i" parm to be
630    treated as a flag (i.e. no argument value is generated).  Also, note the
631    use of arg_dashes to change the type of dashes used on all subsequent
632    options.  The following example is equivalent to the prior.  Note that
633    quote_bash_parm is used instead of including the quotes explicitly.
634
635    command_string = create_command_string('grep', quote_bash_parm('^[^ ]*='),
636        '/tmp/myfile', i=None,  m='1', arg_dashes='--', color='always')
637
638    Result:
639    grep -i -m 1 --color=always '^[^ ]*=' /tmp/myfile
640
641    In the following example, note the automatic quoting of the password
642    option, as per the aforementioned rules.
643
644    command_string = create_command_string('my_pgm', '/tmp/myfile', i=None,
645        m='1', arg_dashes='--', password='${my_pw}')
646
647    However, let's say that the caller wishes to have bash expand the password
648    value.  To achieve this, the caller can use double quotes:
649
650    command_string = create_command_string('my_pgm', '/tmp/myfile', i=None,
651        m='1', arg_dashes='--', password='"${my_pw}"')
652
653    Result:
654    my_pgm -i -m 1 --password="${my_pw}" /tmp/myfile
655
656    command_string = create_command_string('ipmitool', 'power status',
657        I='lanplus', C='3', U='root', P='0penBmc', H='wsbmc010')
658
659    Result:
660    ipmitool -I lanplus -C 3 -U root -P 0penBmc -H wsbmc010 power status
661
662    By default create_command_string will take measures to preserve the order
663    of the callers options.  In some cases, this effort may fail (as when
664    calling directly from a robot program).  In this case, the caller can
665    accept the responsibility of keeping an ordered list of options by calling
666    this function with the last positional parm as some kind of dictionary
667    (preferably an OrderedDict) and avoiding the use of any actual option args.
668
669    Example:
670    kwargs = collections.OrderedDict([('pass', 0), ('fail', 0)])
671    command_string = create_command_string('my program', 'pos_parm1', kwargs)
672
673    Result:
674
675    my program -pass 0 -fail 0 pos_parm1
676
677    Note to programmers who wish to write a wrapper to this function:  If the
678    python version is less than 3.6, to get the options to be processed
679    correctly, the wrapper function must include a _stack_frame_ix_ keyword
680    argument to allow this function to properly re-order options:
681
682    def create_ipmi_ext_command_string(command, **kwargs):
683
684        return create_command_string('ipmitool', command, _stack_frame_ix_=2,
685            **kwargs)
686
687    Example call of wrapper function:
688
689    command_string = create_ipmi_ext_command_string('power status',
690    I='lanplus')
691
692    Description of argument(s):
693    command                         The command (e.g. "cat", "sort",
694                                    "ipmitool", etc.).
695    pos_parms                       The positional parms for the command (e.g.
696                                    PATTERN, FILENAME, etc.).  These will be
697                                    placed at the end of the resulting command
698                                    string.
699    options                         The command options (e.g. "-m 1",
700                                    "--max-count=NUM", etc.).  Note that if
701                                    the value of any option is None, then it
702                                    will be understood to be a flag (for which
703                                    no value is required).
704    """
705
706    arg_dashes = "-"
707    delim = default_arg_delim(arg_dashes)
708
709    command_string = command
710
711    if len(pos_parms) > 0 and gp.is_dict(pos_parms[-1]):
712        # Convert pos_parms from tuple to list.
713        pos_parms = list(pos_parms)
714        # Re-assign options to be the last pos_parm value (which is a
715        # dictionary).
716        options = pos_parms[-1]
717        # Now delete the last pos_parm.
718        del pos_parms[-1]
719    else:
720        # Either get stack_frame_ix from the caller via options or set it to
721        # the default value.
722        stack_frame_ix = options.pop('_stack_frame_ix_', 1)
723        if gm.python_version < gm.ordered_dict_version:
724            # Re-establish the original options order as specified on the
725            # original line of code.  This function depends on correct order.
726            options = re_order_kwargs(stack_frame_ix, **options)
727    for key, value in options.items():
728        # Check for special values in options and process them.
729        if key == "arg_dashes":
730            arg_dashes = str(value)
731            delim = default_arg_delim(arg_dashes)
732            continue
733        if key == "arg_delim":
734            delim = str(value)
735            continue
736        # Format the options elements into the command string.
737        command_string += " " + arg_dashes + key
738        if value is not None:
739            command_string += delim
740            if re.match(r'^(["].*["]|[\'].*[\'])$', str(value)):
741                # Already quoted.
742                command_string += str(value)
743            else:
744                command_string += gm.quote_bash_parm(str(value))
745    # Finally, append the pos_parms to the end of the command_string.  Use
746    # filter to eliminate blank pos parms.
747    command_string = ' '.join([command_string] + list(filter(None, pos_parms)))
748
749    return command_string
750