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