将命令标准输出写入控制台和文件(实时)——Python 3.5 + subprocess.run()

Write command stdout to console and file (live) -- Python 3.5 + subprocess.run()

平台:Python rhel6(64 位)上的 3.5

场景:执行一个Bash命令,其中运行是一个作业。这项工作 returns 几行输出到标准输出,每隔几秒。

命令./run_job --name 'myjob' --config_file ./myconfig.conf

目标:使用Python的subprocess.run(),我正在尝试运行上述命令并捕获进程的标准输出,将其打印到控制台并将其保存到文件中。我需要在 stdout 可用时打印它(实时)。

我试过的:我对此进行了广泛的搜索,我找到的每个解决方案都使用了 subprocess.Popen()This method somewhat worked, but implementing it resulted in breaking the return logic I currently have. Reading through the Python documentationsubprocess.run() 方法是 Python 3.5 推荐的方法,所以这就是我走这条路的原因。

我的设置:到目前为止,我有一个包含日志记录和下面shell命令的通用文件。

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

所以我会使用以下代码 运行 我的脚本:

run_shell("./run_job --name 'myjob' --config_file ./myconfig.conf", fail_msg="Job failed.")

这部分有效,但只有在进程完成时才会打印完整的标准输出。所以终端将挂起直到发生这种情况。我需要以实时方式逐行打印 stdout,以便它可以由记录器写入。

有什么方法可以在不修改现有代码的情况下做到这一点吗?如有任何帮助,我们将不胜感激。

在尝试使用线程和轮询一段时间后,我无法让它正常工作。然而,从 Todd 链接的同一个线程中,我找到了另一种方法来实现我需要的,使用 Popen:

def run_shell(cmd_str, print_stdout=True, fail_msg=""):
    """Run a Linux shell command and return the result code."""

    p = Popen(cmd_str,
              shell=True,
              stderr=STDOUT,
              stdout=PIPE,
              bufsize=1,
              universal_newlines=True)

    logger = logging.getLogger()

    output_lines = list()
    while True:
        output = p.stdout.readline().strip()
        if len(output) == 0 and p.poll() is not None:
            break
        output_lines.append(output)
        if print_stdout:
            logger.info("[cmd] " + output)

    cmd_code = p.returncode
    cmd_stdout = "\n".join(output_lines)
    if cmd_code != 0 and fail_msg != "":
        logger.error(fail_msg)
        exit(-1)

    return cmd_code, cmd_stdout