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