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