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