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