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.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 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 # Get subprocess pid from shell_cmd's call stack. 272 sub_proc = gp.get_stack_var('sub_proc', 0) 273 pid = sub_proc.pid 274 # Terminate the child process. 275 os.kill(pid, signal.SIGTERM) 276 # Restore the original SIGALRM handler. 277 signal.signal(signal.SIGALRM, original_sigalrm_handler) 278 279 return 280 281 282def shell_cmd(command_string, 283 quiet=None, 284 print_output=None, 285 show_err=1, 286 test_mode=0, 287 time_out=None, 288 max_attempts=1, 289 retry_sleep_time=5, 290 allowed_shell_rcs=[0], 291 ignore_err=None, 292 return_stderr=0, 293 fork=0): 294 r""" 295 Run the given command string in a shell and return a tuple consisting of 296 the shell return code and the output. 297 298 Description of argument(s): 299 command_string The command string to be run in a shell 300 (e.g. "ls /tmp"). 301 quiet If set to 0, this function will print 302 "Issuing: <cmd string>" to stdout. When 303 the quiet argument is set to None, this 304 function will assign a default value by 305 searching upward in the stack for the 306 quiet variable value. If no such value is 307 found, quiet is set to 0. 308 print_output If this is set, this function will print 309 the stdout/stderr generated by the shell 310 command to stdout. 311 show_err If show_err is set, this function will 312 print a standardized error report if the 313 shell command fails (i.e. if the shell 314 command returns a shell_rc that is not in 315 allowed_shell_rcs). Note: Error text is 316 only printed if ALL attempts to run the 317 command_string fail. In other words, if 318 the command execution is ultimately 319 successful, initial failures are hidden. 320 test_mode If test_mode is set, this function will 321 not actually run the command. If 322 print_output is also set, this function 323 will print "(test_mode) Issuing: <cmd 324 string>" to stdout. A caller should call 325 shell_cmd directly if they wish to have 326 the command string run unconditionally. 327 They should call the t_shell_cmd wrapper 328 (defined below) if they wish to run the 329 command string only if the prevailing 330 test_mode variable is set to 0. 331 time_out A time-out value expressed in seconds. If 332 the command string has not finished 333 executing within <time_out> seconds, it 334 will be halted and counted as an error. 335 max_attempts The max number of attempts that should be 336 made to run the command string. 337 retry_sleep_time The number of seconds to sleep between 338 attempts. 339 allowed_shell_rcs A list of integers indicating which 340 shell_rc values are not to be considered 341 errors. 342 ignore_err Ignore error means that a failure 343 encountered by running the command string 344 will not be raised as a python exception. 345 When the ignore_err argument is set to 346 None, this function will assign a default 347 value by searching upward in the stack for 348 the ignore_err variable value. If no such 349 value is found, ignore_err is set to 1. 350 return_stderr If return_stderr is set, this function 351 will process the stdout and stderr streams 352 from the shell command separately. In 353 such a case, the tuple returned by this 354 function will consist of three values 355 rather than just two: rc, stdout, stderr. 356 fork Run the command string asynchronously 357 (i.e. don't wait for status of the child 358 process and don't try to get 359 stdout/stderr). 360 """ 361 362 # Assign default values to some of the arguments to this function. 363 quiet = int(gm.dft(quiet, gp.get_stack_var('quiet', 0))) 364 print_output = int(gm.dft(print_output, not quiet)) 365 show_err = int(show_err) 366 global_ignore_err = gp.get_var_value(ignore_err, 1) 367 stack_ignore_err = gp.get_stack_var('ignore_err', global_ignore_err) 368 ignore_err = int(gm.dft(ignore_err, gm.dft(stack_ignore_err, 1))) 369 370 err_msg = gv.svalid_value(command_string) 371 if err_msg != "": 372 raise ValueError(err_msg) 373 374 if not quiet: 375 gp.print_issuing(command_string, test_mode) 376 377 if test_mode: 378 if return_stderr: 379 return 0, "", "" 380 else: 381 return 0, "" 382 383 # Convert each list entry to a signed value. 384 allowed_shell_rcs = fa.source_to_object(allowed_shell_rcs) 385 allowed_shell_rcs = [gm.to_signed(x) for x in allowed_shell_rcs] 386 387 if return_stderr: 388 stderr = subprocess.PIPE 389 else: 390 stderr = subprocess.STDOUT 391 392 shell_rc = 0 393 out_buf = "" 394 err_buf = "" 395 # Write all output to func_history_stdout rather than directly to stdout. 396 # This allows us to decide what to print after all attempts to run the 397 # command string have been made. func_history_stdout will contain the 398 # complete stdout history from the current invocation of this function. 399 func_history_stdout = "" 400 for attempt_num in range(1, max_attempts + 1): 401 sub_proc = subprocess.Popen(command_string, 402 bufsize=1, 403 shell=True, 404 executable='/bin/bash', 405 stdout=subprocess.PIPE, 406 stderr=stderr) 407 out_buf = "" 408 err_buf = "" 409 # Output from this loop iteration is written to func_stdout for later 410 # processing. 411 func_stdout = "" 412 if fork: 413 break 414 command_timed_out = False 415 if time_out is not None: 416 # Designate a SIGALRM handling function and set alarm. 417 signal.signal(signal.SIGALRM, shell_cmd_timed_out) 418 signal.alarm(time_out) 419 try: 420 if return_stderr: 421 for line in sub_proc.stderr: 422 try: 423 err_buf += line 424 except TypeError: 425 line = line.decode("utf-8") 426 err_buf += line 427 if not print_output: 428 continue 429 func_stdout += line 430 for line in sub_proc.stdout: 431 try: 432 out_buf += line 433 except TypeError: 434 line = line.decode("utf-8") 435 out_buf += line 436 if not print_output: 437 continue 438 func_stdout += line 439 except IOError: 440 command_timed_out = True 441 sub_proc.communicate() 442 shell_rc = sub_proc.returncode 443 # Restore the original SIGALRM handler and clear the alarm. 444 signal.signal(signal.SIGALRM, original_sigalrm_handler) 445 signal.alarm(0) 446 if shell_rc in allowed_shell_rcs: 447 break 448 err_msg = "The prior shell command failed.\n" 449 if quiet: 450 err_msg += gp.sprint_var(command_string) 451 if command_timed_out: 452 err_msg += gp.sprint_var(command_timed_out) 453 err_msg += gp.sprint_var(time_out) 454 err_msg += gp.sprint_varx("child_pid", sub_proc.pid) 455 err_msg += gp.sprint_var(attempt_num) 456 err_msg += gp.sprint_var(shell_rc, gp.hexa()) 457 err_msg += gp.sprint_var(allowed_shell_rcs, gp.hexa()) 458 if not print_output: 459 if return_stderr: 460 err_msg += "err_buf:\n" + err_buf 461 err_msg += "out_buf:\n" + out_buf 462 if show_err: 463 func_stdout += gp.sprint_error_report(err_msg) 464 func_history_stdout += func_stdout 465 if attempt_num < max_attempts: 466 func_history_stdout += gp.sprint_issuing("time.sleep(" 467 + str(retry_sleep_time) 468 + ")") 469 time.sleep(retry_sleep_time) 470 471 if shell_rc not in allowed_shell_rcs: 472 func_stdout = func_history_stdout 473 474 gp.gp_print(func_stdout) 475 476 if shell_rc not in allowed_shell_rcs: 477 if not ignore_err: 478 if robot_env: 479 BuiltIn().fail(err_msg) 480 else: 481 raise ValueError("The prior shell command failed.\n") 482 483 if return_stderr: 484 return shell_rc, out_buf, err_buf 485 else: 486 return shell_rc, out_buf 487 488 489def t_shell_cmd(command_string, **kwargs): 490 r""" 491 Search upward in the the call stack to obtain the test_mode argument, add 492 it to kwargs and then call shell_cmd and return the result. 493 494 See shell_cmd prolog for details on all arguments. 495 """ 496 497 if 'test_mode' in kwargs: 498 error_message = "Programmer error - test_mode is not a valid" +\ 499 " argument to this function." 500 gp.print_error_report(error_message) 501 exit(1) 502 503 test_mode = gp.get_stack_var('test_mode', 504 int(gp.get_var_value(None, 0, "test_mode"))) 505 kwargs['test_mode'] = test_mode 506 507 return shell_cmd(command_string, **kwargs) 508 509 510def re_order_kwargs(stack_frame_ix, **kwargs): 511 r""" 512 Re-order the kwargs to match the order in which they were specified on a 513 function invocation and return as an ordered dictionary. 514 515 Note that this re_order_kwargs function should not be necessary in python 516 versions 3.6 and beyond. 517 518 Example: 519 520 The caller calls func1 like this: 521 522 func1('mike', arg1='one', arg2='two', arg3='three') 523 524 And func1 is defined as follows: 525 526 def func1(first_arg, **kwargs): 527 528 kwargs = re_order_kwargs(first_arg_num=2, stack_frame_ix=3, **kwargs) 529 530 The kwargs dictionary before calling re_order_kwargs (where order is not 531 guaranteed): 532 533 kwargs: 534 kwargs[arg3]: three 535 kwargs[arg2]: two 536 kwargs[arg1]: one 537 538 The kwargs dictionary after calling re_order_kwargs: 539 540 kwargs: 541 kwargs[arg1]: one 542 kwargs[arg2]: two 543 kwargs[arg3]: three 544 545 Note that the re-ordered kwargs match the order specified on the call to 546 func1. 547 548 Description of argument(s): 549 stack_frame_ix The stack frame of the function whose 550 kwargs values must be re-ordered. 0 is 551 the stack frame of re_order_kwargs, 1 is 552 the stack from of its caller and so on. 553 kwargs The keyword argument dictionary which is 554 to be re-ordered. 555 """ 556 557 new_kwargs = collections.OrderedDict() 558 559 # Get position number of first keyword on the calling line of code. 560 (args, varargs, keywords, locals) =\ 561 inspect.getargvalues(inspect.stack()[stack_frame_ix][0]) 562 first_kwarg_pos = 1 + len(args) 563 if varargs is not None: 564 first_kwarg_pos += len(locals[varargs]) 565 for arg_num in range(first_kwarg_pos, first_kwarg_pos + len(kwargs)): 566 # This will result in an arg_name value such as "arg1='one'". 567 arg_name = gp.get_arg_name(None, arg_num, stack_frame_ix + 2) 568 # Continuing with the prior example, the following line will result 569 # in key being set to 'arg1'. 570 key = arg_name.split('=')[0] 571 new_kwargs[key] = kwargs[key] 572 573 return new_kwargs 574 575 576def default_arg_delim(arg_dashes): 577 r""" 578 Return the default argument delimiter value for the given arg_dashes value. 579 580 Note: this function is useful for functions that manipulate bash command 581 line arguments (e.g. --parm=1 or -parm 1). 582 583 Description of argument(s): 584 arg_dashes The argument dashes specifier (usually, 585 "-" or "--"). 586 """ 587 588 if arg_dashes == "--": 589 return "=" 590 591 return " " 592 593 594def create_command_string(command, *pos_parms, **options): 595 r""" 596 Create and return a bash command string consisting of the given arguments 597 formatted as text. 598 599 The default formatting of options is as follows: 600 601 <single dash><option name><space delim><option value> 602 603 Example: 604 605 -parm value 606 607 The caller can change the kind of dashes/delimiters used by specifying 608 "arg_dashes" and/or "arg_delims" as options. These options are processed 609 specially by the create_command_string function and do NOT get inserted 610 into the resulting command string. All options following the 611 arg_dashes/arg_delims options will then use the specified values for 612 dashes/delims. In the special case of arg_dashes equal to "--", the 613 arg_delim will automatically be changed to "=". See examples below. 614 615 Quoting rules: 616 617 The create_command_string function will single quote option values as 618 needed to prevent bash expansion. If the caller wishes to defeat this 619 action, they may single or double quote the option value themselves. See 620 examples below. 621 622 pos_parms are NOT automatically quoted. The caller is advised to either 623 explicitly add quotes or to use the quote_bash_parm functions to quote any 624 pos_parms. 625 626 Examples: 627 628 command_string = create_command_string('cd', '~') 629 630 Result: 631 cd ~ 632 633 Note that the pos_parm ("~") does NOT get quoted, as per the 634 aforementioned rules. If quotes are desired, they may be added explicitly 635 by the caller: 636 637 command_string = create_command_string('cd', '\'~\'') 638 639 Result: 640 cd '~' 641 642 command_string = create_command_string('grep', '\'^[^ ]*=\'', 643 '/tmp/myfile', i=None, m='1', arg_dashes='--', color='always') 644 645 Result: 646 grep -i -m 1 --color=always '^[^ ]*=' /tmp/myfile 647 648 In the preceding example, note the use of None to cause the "i" parm to be 649 treated as a flag (i.e. no argument value is generated). Also, note the 650 use of arg_dashes to change the type of dashes used on all subsequent 651 options. The following example is equivalent to the prior. Note that 652 quote_bash_parm is used instead of including the quotes explicitly. 653 654 command_string = create_command_string('grep', quote_bash_parm('^[^ ]*='), 655 '/tmp/myfile', i=None, m='1', arg_dashes='--', color='always') 656 657 Result: 658 grep -i -m 1 --color=always '^[^ ]*=' /tmp/myfile 659 660 In the following example, note the automatic quoting of the password 661 option, as per the aforementioned rules. 662 663 command_string = create_command_string('my_pgm', '/tmp/myfile', i=None, 664 m='1', arg_dashes='--', password='${my_pw}') 665 666 However, let's say that the caller wishes to have bash expand the password 667 value. To achieve this, the caller can use double quotes: 668 669 command_string = create_command_string('my_pgm', '/tmp/myfile', i=None, 670 m='1', arg_dashes='--', password='"${my_pw}"') 671 672 Result: 673 my_pgm -i -m 1 --password="${my_pw}" /tmp/myfile 674 675 command_string = create_command_string('ipmitool', 'power status', 676 I='lanplus', C='3', U='root', P='0penBmc', H='wsbmc010') 677 678 Result: 679 ipmitool -I lanplus -C 3 -U root -P 0penBmc -H wsbmc010 power status 680 681 By default create_command_string will take measures to preserve the order 682 of the callers options. In some cases, this effort may fail (as when 683 calling directly from a robot program). In this case, the caller can 684 accept the responsibility of keeping an ordered list of options by calling 685 this function with the last positional parm as some kind of dictionary 686 (preferably an OrderedDict) and avoiding the use of any actual option args. 687 688 Example: 689 kwargs = collections.OrderedDict([('pass', 0), ('fail', 0)]) 690 command_string = create_command_string('my program', 'pos_parm1', kwargs) 691 692 Result: 693 694 my program -pass 0 -fail 0 pos_parm1 695 696 Note to programmers who wish to write a wrapper to this function: To get 697 the options to be processed correctly, the wrapper function must include a 698 _stack_frame_ix_ keyword argument to allow this function to properly 699 re-order options: 700 701 def create_ipmi_ext_command_string(command, **kwargs): 702 703 return create_command_string('ipmitool', command, _stack_frame_ix_=2, 704 **kwargs) 705 706 Example call of wrapper function: 707 708 command_string = create_ipmi_ext_command_string('power status', 709 I='lanplus') 710 711 Description of argument(s): 712 command The command (e.g. "cat", "sort", 713 "ipmitool", etc.). 714 pos_parms The positional parms for the command (e.g. 715 PATTERN, FILENAME, etc.). These will be 716 placed at the end of the resulting command 717 string. 718 options The command options (e.g. "-m 1", 719 "--max-count=NUM", etc.). Note that if 720 the value of any option is None, then it 721 will be understood to be a flag (for which 722 no value is required). 723 """ 724 725 arg_dashes = "-" 726 delim = default_arg_delim(arg_dashes) 727 728 command_string = command 729 730 if len(pos_parms) > 0 and gp.is_dict(pos_parms[-1]): 731 # Convert pos_parms from tuple to list. 732 pos_parms = list(pos_parms) 733 # Re-assign options to be the last pos_parm value (which is a 734 # dictionary). 735 options = pos_parms[-1] 736 # Now delete the last pos_parm. 737 del pos_parms[-1] 738 else: 739 # Either get stack_frame_ix from the caller via options or set it to 740 # the default value. 741 if '_stack_frame_ix_' in options: 742 stack_frame_ix = options['_stack_frame_ix_'] 743 del options['_stack_frame_ix_'] 744 else: 745 stack_frame_ix = 1 746 # Re-establish the original options order as specified on the 747 # original line of code. This function depends on correct order. 748 options = re_order_kwargs(stack_frame_ix, **options) 749 for key, value in options.items(): 750 # Check for special values in options and process them. 751 if key == "arg_dashes": 752 arg_dashes = str(value) 753 delim = default_arg_delim(arg_dashes) 754 continue 755 if key == "arg_delim": 756 delim = str(value) 757 continue 758 # Format the options elements into the command string. 759 command_string += " " + arg_dashes + key 760 if value is not None: 761 command_string += delim 762 if re.match(r'^(["].*["]|[\'].*[\'])$', str(value)): 763 # Already quoted. 764 command_string += str(value) 765 else: 766 command_string += gm.quote_bash_parm(str(value)) 767 # Finally, append the pos_parms to the end of the command_string. Use 768 # filter to eliminate blank pos parms. 769 command_string = ' '.join([command_string] + list(filter(None, pos_parms))) 770 771 return command_string 772