Записать стандартный вывод команды в консоль и файл (в реальном времени) - Python 3.5 + subprocess.run () - PullRequest
0 голосов
/ 19 марта 2020

Платформа : Python 3.5 на rhel6 (64-разрядная версия)

Сценарий : выполнить команду Bash, которая запускает задание. Это задание возвращает несколько строк вывода на стандартный вывод каждые несколько секунд.

Команда : ./run_job --name 'myjob' --config_file ./myconfig.conf

Цель : использование Python ' s subprocess.run(), я пытаюсь выполнить указанную выше команду и перехватить стандартный вывод процесса, распечатать его на консоли и сохранить в файл. Мне нужно распечатать стандартный вывод, как только он станет доступным (в режиме реального времени).

Что я попробовал : я много раз искал это, и каждое найденное мной решение использовало subprocess.Popen() , Этот метод несколько работал, но его реализация привела к нарушению логики возврата c, которая у меня есть в настоящее время. Для чтения Python документации , метод subprocess.run() рекомендуется с Python 3.5, поэтому я иду по этому маршруту.

Мои настройки : На данный момент у меня есть один общий файл с регистрацией и выполнением команды оболочки ниже.

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 построчно, в режиме реального времени, чтобы он мог быть записан регистратором.

Есть ли способ сделать это, не меняя существующий код? Любая помощь будет оценена.

1 Ответ

0 голосов
/ 23 марта 2020

Пытаясь какое-то время использовать потоки и опросы, я не мог заставить его работать. Однако из той же темы, связанной с Тоддом , я нашел другой способ добиться того, что мне нужно, используя 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
...