Чтение потокового ввода из subprocess.communicate ()

85

Я использую Python subprocess.communicate()для чтения stdout из процесса, который выполняется около минуты.

Как я могу распечатать каждую строку этого процесса stdoutв потоковом режиме, чтобы я мог видеть результат в том виде, в каком он сгенерирован, но по-прежнему блокировать завершение процесса перед продолжением?

subprocess.communicate() кажется, дает сразу весь результат.

Генрих Шметтерлинг
источник

Ответы:

46

Обратите внимание, я думаю, что метод Дж. Ф. Себастьяна (ниже) лучше.


Вот простой пример (без проверки на ошибки):

import subprocess
proc = subprocess.Popen('ls',
                       shell=True,
                       stdout=subprocess.PIPE,
                       )
while proc.poll() is None:
    output = proc.stdout.readline()
    print output,

Если lsзаканчивается слишком быстро, цикл while может завершиться до того, как вы прочитаете все данные.

Вы можете поймать остаток в stdout следующим образом:

output = proc.communicate()[0]
print output,
Unutbu
источник
1
не становится ли эта схема жертвой проблемы блокировки буфера, о которой говорится в документе Python?
Генрих Шметтерлинг
@Heinrich, я плохо понимаю проблему блокировки буфера. Я считаю (просто погуглил), что эта проблема возникает только в том случае, если вы не читаете из stdout (и stderr?) Внутри цикла while. Поэтому я думаю, что приведенный выше код в порядке, но я не могу сказать наверняка.
unutbu
1
На самом деле здесь есть проблема с блокировкой, несколько лет назад у меня не было конца этой проблеме, когда строка чтения блокировалась до тех пор, пока не появилась новая строка, даже если процесс завершился. Я не помню решение, но я думаю, что оно как-то связано с чтением рабочего потока и просто зацикливанием while proc.poll() is None: time.sleep(0)или чем-то в этом роде. По сути, вам нужно либо убедиться, что вывод новой строки - это последнее, что делает процесс (потому что вы не можете дать интерпретатору время для повторного цикла), либо вам нужно сделать что-то «необычное».
dash-tom-bang
@Heinrich: Алекс Мартелли пишет о том, как избежать тупика здесь: stackoverflow.com/questions/1445627/…
unutbu
6
Блокировка буфера проще, чем иногда кажется: родительские блоки ждут выхода дочернего элемента + дочерние блоки ждут, пока родительский элемент прочитает и освободит некоторое пространство в канале связи, который заполнен = тупик. Это так просто. Чем меньше труба, тем больше вероятность.
MarcH
163

Чтобы получить вывод подпроцесса построчно, как только подпроцесс очищает свой буфер stdout:

#!/usr/bin/env python2
from subprocess import Popen, PIPE

p = Popen(["cmd", "arg1"], stdout=PIPE, bufsize=1)
with p.stdout:
    for line in iter(p.stdout.readline, b''):
        print line,
p.wait() # wait for the subprocess to exit

iter()используется для чтения строк, как только они написаны, для обхода ошибки упреждающего чтения в Python 2 .

Если stdout подпроцесса использует блочную буферизацию вместо буферизации строки в неинтерактивном режиме (что приводит к задержке вывода до тех пор, пока дочерний буфер не заполнится или не будет явно очищен дочерним элементом), вы можете попытаться принудительно выполнить небуферизованный вывод, используя pexpect, ptyмодули или unbuffer, stdbuf, scriptутилиты , см Q: Почему бы не просто использовать трубу (POPEN ())?


Вот код Python 3:

#!/usr/bin/env python3
from subprocess import Popen, PIPE

with Popen(["cmd", "arg1"], stdout=PIPE, bufsize=1,
           universal_newlines=True) as p:
    for line in p.stdout:
        print(line, end='')

Примечание. В отличие от Python 2, который выводит байтовые строки подпроцесса как есть; Python 3 использует текстовый режим (вывод cmd декодируется с использованием locale.getpreferredencoding(False)кодировки).

jfs
источник
что означает буква b?
Аарон
4
b''является bytesлитералом в Python 2.7 и Python 3.
jfs
2
@JinghaoShi: bufsize=1может иметь значение, если вы также пишете (используете p.stdin) в подпроцесс, например, это может помочь избежать тупика при выполнении интерактивного ( pexpect-подобного) обмена - при условии, что в самом дочернем процессе нет проблем с буферизацией. Если вы только читаете, то, как я уже сказал, разница только в производительности: если это не так, не могли бы вы предоставить минимальный полный пример кода, который это показывает?
jfs
1
@ealeon: да. Для этого требуются методы, которые могут читать stdout / stderr одновременно, если вы не объедините stderr с stdout (путем перехода stderr=subprocess.STDOUTк Popen()). См. Также ссылки на решения для потоковой передачи или asyncio .
jfs
2
@saulspatz, если stdout=PIPEне захватывает вывод (вы все еще видите его на экране), тогда ваша программа может вместо этого печатать на stderr или напрямую на терминал. Чтобы объединить stdout и stderr, пройдите stderr=subprocess.STDOUT(см. Мой предыдущий комментарий). Для захвата вывода, распечатанного непосредственно на вашем tty, вы можете использовать решения pexpect, pty. . Вот более сложный пример кода .
jfs
6

Я считаю, что самый простой способ собрать вывод процесса в потоковом режиме выглядит так:

import sys
from subprocess import *
proc = Popen('ls', shell=True, stdout=PIPE)
while True:
    data = proc.stdout.readline()   # Alternatively proc.stdout.read(1024)
    if len(data) == 0:
        break
    sys.stdout.write(data)   # sys.stdout.buffer.write(data) on Python 3.x

Функция readline()or read()должна возвращать только пустую строку в EOF после завершения процесса - в противном случае она будет блокироваться, если нечего читать ( readline()включает новую строку, поэтому в пустых строках она возвращает "\ n"). Это позволяет избежать неудобного финального communicate()вызова после цикла.

Для файлов с очень длинными строками read()может быть предпочтительнее уменьшить максимальное использование памяти - число, передаваемое в него, является произвольным, но его исключение приводит к одновременному чтению всего вывода канала, что, вероятно, нежелательно.

D Coetzee
источник
4
data = proc.stdout.read()блоки, пока не будут прочитаны все данные. Вы можете спутать это с тем, os.read(fd, maxsize)что можно вернуть раньше (как только будут доступны какие-либо данные).
jfs
Вы правы, я ошибся. Однако, если в качестве аргумента передается разумное количество байтов, read()он работает нормально, а также readline()отлично работает до тех пор, пока максимальная длина строки является разумной. Соответственно обновил свой ответ.
D Coetzee
3

Если вам нужен неблокирующий подход, не используйте process.communicate(). Если вы установите для subprocess.Popen()аргумента stdoutзначение PIPE, вы можете читать process.stdoutи проверять, выполняется ли процесс с использованием process.poll().

Лукаш Лалински
источник
1
неблокирующий подход
непростая задача
3

Если вы просто пытаетесь передать вывод в реальном времени, трудно сделать это проще:

import subprocess

# This will raise a CalledProcessError if the program return a nonzero code.
# You can use call() instead if you don't care about that case.
subprocess.check_call(['ls', '-l'])

См. Документацию для subprocess.check_call () .

Если вам нужно обработать вывод, конечно, зацикливайтесь на нем. Но если вы этого не сделаете, просто сделайте это просто.

Изменить: JF Себастьян указывает на то, что значения по умолчанию для параметров stdout и stderr передаются в sys.stdout и sys.stderr, и что это не удастся, если sys.stdout и sys.stderr были заменены (например, для захвата вывода в тесты).

Нейт
источник
Это не будет работать , если sys.stdoutили sys.stderrзаменяются файл-подобными объектами , которые не имеют никакого реального fileno (). Если sys.stdout, sys.stderrне будут заменены , то это еще проще: subprocess.check_call(args).
jfs
Благодаря! Я осознавал причуды замены sys.stdout / stderr, но почему-то никогда не осознавал, что если вы опустите аргументы, он передаст stdout и stderr в нужные места. Мне нравится call()более , check_call()если я не хочу, чтобы CalledProcessError.
Nate
python -mthis: "Ошибки никогда не должны передаваться без уведомления. Если не отключены явно." именно поэтому пример кода следует предпочесть check_call()более call().
jfs
Хех. Многие программы, которые я заканчиваю, call()возвращают ненулевые коды ошибок в условиях отсутствия ошибок, потому что они ужасны. В нашем случае ненулевой код ошибки на самом деле не является ошибкой.
Nate
да. Существуют такие программы, grepкоторые могут возвращать ненулевой статус выхода, даже если нет ошибки - это исключения. По умолчанию нулевой статус выхода указывает на успех.
jfs
1
myCommand="ls -l"
cmd=myCommand.split()
# "universal newline support" This will cause to interpret \n, \r\n and \r     equally, each as a newline.
p = subprocess.Popen(cmd, stderr=subprocess.PIPE, universal_newlines=True)
while True:    
    print(p.stderr.readline().rstrip('\r\n'))
Петр Дж
источник
1
Всегда полезно объяснить, что делает ваше решение, просто для того, чтобы люди лучше понимали
ДаФоис
2
Вам следует подумать об использовании shlex.split(myCommand)вместо myCommand.split(). Он также учитывает пробелы в цитируемых аргументах.
UtahJarhead
0

Добавление еще одного решения python3 с небольшими изменениями:

  1. Позволяет поймать код выхода процесса оболочки (мне не удалось получить код выхода при использовании withконструкции)
  2. Также выводит stderr в режиме реального времени
import subprocess
import sys
def subcall_stream(cmd, fail_on_error=True):
    # Run a shell command, streaming output to STDOUT in real time
    # Expects a list style command, e.g. `["docker", "pull", "ubuntu"]`
    p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, bufsize=1, universal_newlines=True)
    for line in p.stdout:
        sys.stdout.write(line)
    p.wait()
    exit_code = p.returncode
    if exit_code != 0 and fail_on_error:
        raise RuntimeError(f"Shell command failed with exit code {exit_code}. Command: `{cmd}`")
    return(exit_code)
снежный человек56
источник