Platform: Python 3.5 on rhel6 (64-bit)
Scenario: Execute a Bash command which runs a job. This job returns several lines of output to stdout, every few seconds.
Command: ./run_job --name 'myjob' --config_file ./myconfig.conf
Goal: Using Python's subprocess.run()
, I am trying to run the above command and capture the stdout of the process, print it to the console and also save it to a file. I need the stdout to be printed as it becomes available (live).
What I've Tried: I have searched extensively for this, and every solution I found was using subprocess.Popen()
. This method somewhat worked, but implementing it resulted in breaking the return logic I currently have. Reading through the Python documentation, the subprocess.run()
method is the recommended way as of Python 3.5, so that's why I am going this route.
My Setup: So far, I have one common file with the logging and running the shell command below.
def setup_logging(log_lvl="INFO"):
script_name = path.splitext(path.basename(__file__))[0]
log_path = environ["HOME"] + "/logs/" + script_name + ".log"
logging.basicConfig(
level=getattr(logging, log_lvl.upper()),
format="%(asctime)s: [%(levelname)s] %(message)s",
handlers=[
logging.FileHandler(filename=log_path, mode="w", encoding="utf-8"),
logging.StreamHandler()
]
)
def run_shell(cmd_str, print_stdout=True, fail_msg=""):
logger = logging.getLogger()
result = run(cmd_str, universal_newlines=True, shell=True, stderr=STDOUT, stdout=PIPE)
cmd_stdout = result.stdout.strip()
cmd_code = result.returncode
if print_stdout and cmd_stdout != "":
logger.info("[OUT] " + cmd_stdout)
if cmd_code != 0 and fail_msg != "":
logger.error(fail_msg)
exit(cmd_code)
return cmd_code, cmd_stdout
So I would use the following code to run my script:
run_shell("./run_job --name 'myjob' --config_file ./myconfig.conf", fail_msg="Job failed.")
This partially works, but the full stdout is printed only when the process has completed. So the terminal will hang until that happens. I need to print the stdout line by line, in a live manner, so that it can be written by the logger.
Is there any way to do this without overhauling my existing code? Any help would be appreciated.