1#!/usr/bin/env python 2 3r""" 4See help text for details. 5""" 6 7import sys 8import subprocess 9import re 10 11save_path_0 = sys.path[0] 12del sys.path[0] 13 14from gen_arg import * 15from gen_print import * 16from gen_valid import * 17from gen_misc import * 18from gen_cmd import * 19from var_funcs import * 20 21# Restore sys.path[0]. 22sys.path.insert(0, save_path_0) 23 24# Set exit_on_error for gen_valid functions. 25set_exit_on_error(True) 26 27parser = argparse.ArgumentParser( 28 usage='%(prog)s [OPTIONS]', 29 description="%(prog)s will create a status file path name adhering to the" 30 + " following pattern: <status dir path>/<prefix>.yymmdd." 31 + "hhmmss.status. It will then run the command string and" 32 + " direct its stdout/stderr to the status file and optionally" 33 + " to stdout. This dual output streaming will be" 34 + " accomplished using either the \"script\" or the \"tee\"" 35 + " program. %(prog)s will also set and export environment" 36 + " variable \"AUTO_STATUS_FILE_PATH\" for the benefit of" 37 + " child programs.", 38 formatter_class=argparse.ArgumentDefaultsHelpFormatter, 39 prefix_chars='-+') 40 41parser.add_argument( 42 '--status_dir_path', 43 default='', 44 help="The path to the directory where the status file will be created." 45 + "%(default)s The default value is obtained from environment" 46 + " variable \"${STATUS_DIR_PATH}\", if set or from \"${HOME}/" 47 + "status/\".") 48 49parser.add_argument( 50 '--prefix', 51 default='', 52 help="The prefix for the generated file name.%(default)s The default value" 53 + " is the command portion (i.e. the first token) of the command" 54 + " string.") 55 56parser.add_argument( 57 '--status_file_name', 58 default='', 59 help="This allows the user to explicitly specify the status file name. If" 60 + " this argument is not used, %(prog)s composes a status file name." 61 + " If this argument is specified, the \"--prefix\" argument is" 62 + " ignored.") 63 64parser.add_argument( 65 '--stdout', 66 default=1, 67 type=int, 68 choices=[1, 0], 69 help="Indicates that stdout/stderr from the command string execution" 70 + " should be written to stdout as well as to the status file.") 71 72parser.add_argument( 73 '--tee', 74 default=0, 75 type=int, 76 choices=[1, 0], 77 help="Indicates that \"tee\" rather than \"script\" should be used.") 78 79parser.add_argument( 80 '--show_url', 81 default=0, 82 type=int, 83 choices=[1, 0], 84 help="Indicates that the status file path shown should be shown in the" 85 + " form of a url. If the output is to be viewed from a browser," 86 + " this may well become a clickable link. Note that the" 87 + " get_file_path_url.py program must be found in the \"PATH\"" 88 + " environment variable for this argument to be effective.") 89 90parser.add_argument( 91 'command_string', 92 default='', 93 nargs='*', 94 help="The command string to be run.%(default)s") 95 96# Populate stock_list with options we want. 97stock_list = [("test_mode", 0), ("quiet", 1), ("debug", 0)] 98 99 100def exit_function(signal_number=0, 101 frame=None): 102 r""" 103 Execute whenever the program ends normally or with the signals that we catch (i.e. TERM, INT). 104 """ 105 106 dprint_executing() 107 dprint_var(signal_number) 108 109 qprint_pgm_footer() 110 111 112def signal_handler(signal_number, 113 frame): 114 r""" 115 Handle signals. Without a function to catch a SIGTERM or SIGINT, our program would terminate immediately 116 with return code 143 and without calling our exit_function. 117 """ 118 119 # Our convention is to set up exit_function with atexit.register() so there is no need to explicitly 120 # call exit_function from here. 121 122 dprint_executing() 123 124 # Calling exit prevents us from returning to the code that was running when we received the signal. 125 exit(0) 126 127 128def validate_parms(): 129 r""" 130 Validate program parameters, etc. 131 """ 132 133 global status_dir_path 134 global command_string 135 136 # Convert command_string from list to string. 137 command_string = " ".join(command_string) 138 set_pgm_arg(command_string) 139 valid_value(command_string) 140 141 if status_dir_path == "": 142 status_dir_path = \ 143 os.environ.get("STATUS_DIR_PATH", 144 os.environ.get("HOME") + "/status/") 145 status_dir_path = add_trailing_slash(status_dir_path) 146 set_pgm_arg(status_dir_path) 147 valid_dir_path(status_dir_path) 148 149 global prefix 150 global status_file_name 151 if status_file_name == "": 152 if prefix == "": 153 prefix = command_string.split(" ")[0] 154 # File extensions (e.g. ".sh", ".py", .etc), look clumsy in status file names. 155 extension_regex = "\\.[a-zA-Z0-9]{1,3}$" 156 prefix = re.sub(extension_regex, "", prefix) 157 set_pgm_arg(prefix) 158 status_file_name = prefix + "." + file_date_time_stamp() + ".status" 159 set_pgm_arg(status_file_name) 160 161 global status_file_path 162 163 status_file_path = status_dir_path + status_file_name 164 # Set environment variable for the benefit of child programs. 165 os.environ['AUTO_STATUS_FILE_PATH'] = status_file_path 166 # Set deprecated but still used AUTOSCRIPT_STATUS_FILE_PATH value. 167 os.environ['AUTOSCRIPT_STATUS_FILE_PATH'] = status_file_path 168 169 gen_post_validation(exit_function, signal_handler) 170 171 172def script_func(command_string, status_file_path): 173 r""" 174 Run the command string producing both stdout and file output via the script command and return the 175 shell_rc. 176 177 Description of argument(s): 178 command_string The command string to be run. 179 status_file_path The path to the status file which is to contain a copy of all stdout. 180 """ 181 182 cmd_buf = "script -a -q -f " + status_file_path + " -c '" \ 183 + escape_bash_quotes(command_string) + " ; printf \"\\n" \ 184 + sprint_varx(ret_code_str, "${?}").rstrip("\n") + "\\n\"'" 185 qprint_issuing(cmd_buf) 186 sub_proc = subprocess.Popen(cmd_buf, shell=True) 187 sub_proc.communicate() 188 shell_rc = sub_proc.returncode 189 190 # Retrieve return code by examining ret_code_str output statement from status file. 191 # Example text to be analyzed. 192 # auto_status_file_ret_code: 127 193 cmd_buf = "tail -n 10 " + status_file_path + " | egrep -a \"" \ 194 + ret_code_str + ":[ ]+\"" 195 rc, output = shell_cmd(cmd_buf) 196 key, value = parse_key_value(output) 197 shell_rc = int(value) 198 199 return shell_rc 200 201 202def tee_func(command_string, status_file_path): 203 r""" 204 Run the command string producing both stdout and file output via the tee command and return the shell_rc. 205 206 Description of argument(s): 207 command_string The command string to be run. 208 status_file_path The path to the status file which is to contain a copy of all stdout. 209 """ 210 211 cmd_buf = "set -o pipefail ; " + command_string + " 2>&1 | tee -a " \ 212 + status_file_path 213 qprint_issuing(cmd_buf) 214 sub_proc = subprocess.Popen(cmd_buf, shell=True) 215 sub_proc.communicate() 216 shell_rc = sub_proc.returncode 217 218 print 219 print_varx(ret_code_str, shell_rc) 220 with open(status_file_path, "a") as status_file: 221 # Append ret code string and status_file_path to end of status file. 222 status_file.write("\n" + sprint_varx(ret_code_str, shell_rc)) 223 224 return shell_rc 225 226 227def main(): 228 229 gen_get_options(parser, stock_list) 230 231 validate_parms() 232 233 qprint_pgm_header() 234 235 global ret_code_str 236 ret_code_str = re.sub("\\.py$", "", pgm_name) + "_ret_code" 237 238 global show_url 239 if show_url: 240 shell_rc, output = shell_cmd("which get_file_path_url.py", show_err=0) 241 if shell_rc != 0: 242 show_url = 0 243 set_pgm_arg(show_url) 244 else: 245 shell_rc, status_file_url = shell_cmd("get_file_path_url.py " 246 + status_file_path) 247 status_file_url = status_file_url.rstrip("\n") 248 249 # Print status file path/url to stdout and to status file. 250 with open(status_file_path, "w+") as status_file: 251 if show_url: 252 print_var(status_file_url) 253 status_file.write(sprint_var(status_file_url)) 254 else: 255 print_var(status_file_path) 256 status_file.write(sprint_var(status_file_path)) 257 258 if stdout: 259 if tee: 260 shell_rc = tee_func(command_string, status_file_path) 261 else: 262 shell_rc = script_func(command_string, status_file_path) 263 if show_url: 264 print_var(status_file_url) 265 else: 266 print_var(status_file_path) 267 else: 268 cmd_buf = command_string + " >> " + status_file_path + " 2>&1" 269 shell_rc, output = shell_cmd(cmd_buf, show_err=0) 270 with open(status_file_path, "a") as status_file: 271 # Append ret code string and status_file_path to end of status 272 # file. 273 status_file.write("\n" + sprint_varx(ret_code_str, shell_rc)) 274 275 # Append status_file_path print statement to end of status file. 276 with open(status_file_path, "a") as status_file: 277 if show_url: 278 status_file.write(sprint_var(status_file_url)) 279 else: 280 status_file.write(sprint_var(status_file_path)) 281 exit(shell_rc) 282 283 284main() 285