Печать выходных строк стандартного вывода подпроцесса

Я создал простую функцию Python:

import subprocess
from io import TextIOWrapper


def run_shell_command(command: list, debug: bool = False):
    '''
    Run shell command

    :param command: Shell command
    :param debug: Debug mode
    :return: Result code and message
    '''
    try:
        process = subprocess.run(
            command, check=True, text=True, timeout=5,
            stdout=subprocess.PIPE, stderr=subprocess.STDOUT
        )
        if debug:
            for line in TextIOWrapper(process.stdout, encoding='utf-8'):
                print(line)
        message = 'Shell command executed sucessfully'
        return ({'code': 200, 'msg': message, 'stdout': process.stdout})
    except subprocess.CalledProcessError as e:
        return ({'code': 500, 'msg': e.output})


if __name__ == "__main__":
    command = run_shell_command(['ls', '-lah'], True)
    print(command)

Когда я запускаю его в режиме отладки, я получаю следующую ошибку:

Traceback (most recent call last):
  File "/tmp/command.py", line 28, in <module>
    command = run_shell_command(['ls', '-lah'], True)
  File "/tmp/command.py", line 19, in run_shell_command
    for line in TextIOWrapper(process.stdout, encoding = "utf-8"):
AttributeError: 'str' object has no attribute 'readable'

Запуская Python 3.9 на сервере Linux, мне было интересно, можете ли вы рассказать, в чем может быть проблема. При отключенной отладке я получаю правильный текстовый вывод. Спасибо за помощь.

Обновлено: Судя по комментариям ниже, исправить это довольно просто:

        if debug:
            print(process.stdout.rstrip())

Однако принятое решение IMO лучше по сравнению с исходным кодом.

subprocess.run выходит после завершения процесса. process.stdout содержит строковый вывод (не канал или файл) команды. Простой print(process.stdout) вместо цикла for должен вывести весь результат.
Michael Butscher 04.08.2024 18:21

Можете ли вы объяснить, почему вы передаете process.stdout, который здесь является str (потому что вы использовали text=True в subprocess.run), на TextIOWrapper, но он не принимает str. почему ты используешь TextIOWrapper?

juanpa.arrivillaga 04.08.2024 18:22

Я думаю, возможно, вы ожидали, что subprocess.run вернет объект Popen, но это не так, он возвращает объект CompletedProcess

juanpa.arrivillaga 04.08.2024 18:23

@juanpa.arrivillaga Изначально я смотрел этот комментарий: stackoverflow.com/a/34938743/14684366

Floren 04.08.2024 18:24

@Флорен, верно, ты не работаешь с объектом Popen

juanpa.arrivillaga 04.08.2024 18:25

Спасибо @MichaelButscher, я отредактировал вопрос с вашим простым исправлением.

Floren 04.08.2024 18:41

@Floren Это будет распечатывать выходные данные только после завершения процесса, а не «построчно».

AKX 04.08.2024 18:43

@AKX, меня это устраивает, я отредактирую заголовок вопроса. Спасибо, что дали мне знать.

Floren 04.08.2024 18:51
Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
1
8
67
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

К сожалению, «простой» подход не позволяет справиться со всеми крайними случаями — вам нужно будет прочитать подпроцесс stdout во время его потоковой передачи, распечатать его и накопить в буфере, а также отслеживать время, чтобы вы могли тайм-аут правильно. Обратите внимание, что здесь тоже возможна ошибка (хотя и не очень опасная), если чтение 4096 байт заканчивается многострочным символом.

def run_shell_command(command: list, debug: bool = False, timeout: float = 5):
    """
    Run shell command

    :param command: Shell command
    :param debug: Debug mode
    :return: Result code and message
    """
    with subprocess.Popen(
        command,
        stdout=subprocess.PIPE,
        stderr=subprocess.STDOUT,
    ) as process:
        start_time = time.time()
        output = b""
        while True:
            buf = process.stdout.read(4096)
            if debug:
                # could fail if `buf` happens to end in a multi-byte character
                print(buf.decode("utf-8", "ignore"))
            output += buf

            if time.time() - start_time > timeout:
                process.kill()
                message = "Shell command timed out"
                return {"code": 500, "msg": message, "stdout": output}

            if process.poll() is not None:
                break
    if process.returncode != 0:
        message = "Shell command failed"
        return {"code": 500, "msg": message, "stdout": output}

    message = "Shell command executed successfully"
    return {"code": 200, "msg": message, "stdout": output}

if __name__ == "__main__":
    command = run_shell_command(["ls", "-lah"], True)
    print(command)

Правильный способ обработки многобайтовых символов, разделенных по границам блоков, — это использование инкрементного декодера.

user2357112 04.08.2024 18:39

Абсолютно. Или просто продолжайте накапливать новые фрагменты, пока строка не станет декодируемой. Но поскольку это предназначено только для вывода отладки, я решил сделать его немного проще.

AKX 04.08.2024 18:43

Спасибо @AKX за решение исходного вопроса. Я отмечу ваш ответ как принятое решение, как более полное.

Floren 04.08.2024 18:56

@AKX, линтер возвращает это предупреждение: «Рассмотрите возможность использования 'with' для операций выделения ресурсов». на subprocess.Popen. Мне было интересно, какая альтернатива коду могла бы это исправить?

Floren 04.08.2024 19:07

@Флорен Конечно – дополнен ответ, чтобы использовать with для управления ресурсами.

AKX 04.08.2024 19:14

Спасибо @AKX, есть кое-что интересное при запуске команды git clone или git branch. Печатается не весь вывод, мне интересно, в чем основная причина?

Floren 04.08.2024 19:42

Чего не хватает?

AKX 04.08.2024 20:51

Если я запущу git clone repo someurl, фактический результат клонирования не отобразится. Я думаю, именно так работает github.

Floren 04.08.2024 22:24

Верно, это, по-видимому, git просто делает разные вещи в зависимости от того, подключено ли оно к устройству TTY (а если вы перенаправляете вывод, то нет). См. git clone --progress может быть?

AKX 05.08.2024 07:06

Это решило проблему, спасибо!

Floren 05.08.2024 22:03

Другие вопросы по теме