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 104 catch (i.e. TERM, INT). 105 """ 106 107 dprint_executing() 108 dprint_var(signal_number) 109 110 qprint_pgm_footer() 111 112 113def signal_handler(signal_number, 114 frame): 115 r""" 116 Handle signals. Without a function to catch a SIGTERM or SIGINT, our 117 program would terminate immediately with return code 143 and without 118 calling our exit_function. 119 """ 120 121 # Our convention is to set up exit_function with atexit.register() so 122 # there is no need to explicitly call exit_function from here. 123 124 dprint_executing() 125 126 # Calling exit prevents us from returning to the code that was running 127 # when we received the signal. 128 exit(0) 129 130 131def validate_parms(): 132 r""" 133 Validate program parameters, etc. 134 """ 135 136 global status_dir_path 137 global command_string 138 139 # Convert command_string from list to string. 140 command_string = " ".join(command_string) 141 set_pgm_arg(command_string) 142 valid_value(command_string) 143 144 if status_dir_path == "": 145 status_dir_path = \ 146 os.environ.get("STATUS_DIR_PATH", 147 os.environ.get("HOME") + "/status/") 148 status_dir_path = add_trailing_slash(status_dir_path) 149 set_pgm_arg(status_dir_path) 150 valid_dir_path(status_dir_path) 151 152 global prefix 153 global status_file_name 154 if status_file_name == "": 155 if prefix == "": 156 prefix = command_string.split(" ")[0] 157 # File extensions (e.g. ".sh", ".py", .etc), look clumsy in 158 # status file names. 159 extension_regex = "\\.[a-zA-Z0-9]{1,3}$" 160 prefix = re.sub(extension_regex, "", prefix) 161 set_pgm_arg(prefix) 162 status_file_name = prefix + "." + file_date_time_stamp() + ".status" 163 set_pgm_arg(status_file_name) 164 165 global status_file_path 166 167 status_file_path = status_dir_path + status_file_name 168 # Set environment variable for the benefit of child programs. 169 os.environ['AUTO_STATUS_FILE_PATH'] = status_file_path 170 # Set deprecated but still used AUTOSCRIPT_STATUS_FILE_PATH value. 171 os.environ['AUTOSCRIPT_STATUS_FILE_PATH'] = status_file_path 172 173 gen_post_validation(exit_function, signal_handler) 174 175 176def script_func(command_string, status_file_path): 177 r""" 178 Run the command string producing both stdout and file output via the 179 script command and return the shell_rc. 180 181 Description of argument(s): 182 command_string The command string to be run. 183 status_file_path The path to the status file which is to 184 contain a copy of all stdout. 185 """ 186 187 cmd_buf = "script -a -q -f " + status_file_path + " -c '" \ 188 + escape_bash_quotes(command_string) + " ; printf \"\\n" \ 189 + sprint_varx(ret_code_str, "${?}").rstrip("\n") + "\\n\"'" 190 qprint_issuing(cmd_buf) 191 sub_proc = subprocess.Popen(cmd_buf, shell=True) 192 sub_proc.communicate() 193 shell_rc = sub_proc.returncode 194 195 # Retrieve return code by examining ret_code_str output statement from 196 # status file. 197 # Example text to be analyzed. 198 # auto_status_file_ret_code: 127 199 cmd_buf = "tail -n 10 " + status_file_path + " | egrep -a \"" \ 200 + ret_code_str + ":[ ]+\"" 201 rc, output = shell_cmd(cmd_buf) 202 key, value = parse_key_value(output) 203 shell_rc = int(value) 204 205 return shell_rc 206 207 208def tee_func(command_string, status_file_path): 209 r""" 210 Run the command string producing both stdout and file output via the tee 211 command and return the shell_rc. 212 213 Description of argument(s): 214 command_string The command string to be run. 215 status_file_path The path to the status file which is to 216 contain a copy of all stdout. 217 """ 218 219 cmd_buf = "set -o pipefail ; " + command_string + " 2>&1 | tee -a " \ 220 + status_file_path 221 qprint_issuing(cmd_buf) 222 sub_proc = subprocess.Popen(cmd_buf, shell=True) 223 sub_proc.communicate() 224 shell_rc = sub_proc.returncode 225 226 print 227 print_varx(ret_code_str, shell_rc) 228 with open(status_file_path, "a") as status_file: 229 # Append ret code string and status_file_path to end of status file. 230 status_file.write("\n" + sprint_varx(ret_code_str, shell_rc)) 231 232 return shell_rc 233 234 235def main(): 236 237 gen_get_options(parser, stock_list) 238 239 validate_parms() 240 241 qprint_pgm_header() 242 243 global ret_code_str 244 ret_code_str = re.sub("\\.py$", "", pgm_name) + "_ret_code" 245 246 global show_url 247 if show_url: 248 shell_rc, output = shell_cmd("which get_file_path_url.py", show_err=0) 249 if shell_rc != 0: 250 show_url = 0 251 set_pgm_arg(show_url) 252 else: 253 shell_rc, status_file_url = shell_cmd("get_file_path_url.py " 254 + status_file_path) 255 status_file_url = status_file_url.rstrip("\n") 256 257 # Print status file path/url to stdout and to status file. 258 with open(status_file_path, "w+") as status_file: 259 if show_url: 260 print_var(status_file_url) 261 status_file.write(sprint_var(status_file_url)) 262 else: 263 print_var(status_file_path) 264 status_file.write(sprint_var(status_file_path)) 265 266 if stdout: 267 if tee: 268 shell_rc = tee_func(command_string, status_file_path) 269 else: 270 shell_rc = script_func(command_string, status_file_path) 271 if show_url: 272 print_var(status_file_url) 273 else: 274 print_var(status_file_path) 275 else: 276 cmd_buf = command_string + " >> " + status_file_path + " 2>&1" 277 shell_rc, output = shell_cmd(cmd_buf, show_err=0) 278 with open(status_file_path, "a") as status_file: 279 # Append ret code string and status_file_path to end of status 280 # file. 281 status_file.write("\n" + sprint_varx(ret_code_str, shell_rc)) 282 283 # Append status_file_path print statement to end of status file. 284 with open(status_file_path, "a") as status_file: 285 if show_url: 286 status_file.write(sprint_var(status_file_url)) 287 else: 288 status_file.write(sprint_var(status_file_path)) 289 exit(shell_rc) 290 291 292main() 293