Зчитування потокового введення з subprocess.communicate ()


84

Я використовую Python's subprocess.communicate() для читання stdout із процесу, який триває близько хвилини.

Як я можу роздрукувати кожен рядок цього процесу stdout в режимі потокового передавання, щоб я міг бачити результат, як він генерується, але все одно блокувати процес, що закінчується, перш ніж продовжувати?

subprocess.communicate() з'являється, щоб дати всі вихідні дані відразу.


Відповіді:


44

Зверніть увагу, я думаю , що метод Дж. Ф. Себастьяна (нижче) кращий.


Ось простий приклад (без перевірки на наявність помилок):

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,

1
ця схема стає жертвою проблеми блокування буфера, на яку посилається python doc?
Генріх Шметтерлінг,

@Heinrich, проблема блокування буфера - це не те, що я добре розумію. Я вважаю (просто з гуглиння), що ця проблема виникає лише в тому випадку, якщо ви не читаєте з stdout (і stderr?) Всередині циклу while. Тому я думаю, що наведений вище код добре, але я не можу сказати точно.
unutbu

1
Це насправді страждає від проблеми блокування, кілька років тому у мене не було кінця проблеми, коли readline буде блокувати, поки вона не отримає новий рядок, навіть якщо процес закінчився. Я не пам’ятаю рішення, але я думаю, що це мало щось спільне з виконанням зчитування в робочому потоці і просто циклічним while proc.poll() is None: time.sleep(0)чи таким чином. По суті - вам потрібно або переконатися, що вихідний рядок - це останнє, що робить процес (оскільки ви не можете дати інтерпретатору час повторити цикл), або вам потрібно зробити щось «фантазійне».
dash-tom-bang

@Heinrich: Алекс Мартеллі пише про те , як уникнути тупикової ситуації тут: stackoverflow.com/questions/1445627 / ...
unutbu

6
Блокування буфера простіше, ніж це іноді звучить: батьківські блоки, які чекають виходу дитини + дочірні блоки, які чекають батьків, щоб прочитати і звільнити трохи місця в комунікаційній трубі, яке заповнене = тупик. Це так просто. Чим менше труба, тим більше ймовірність цього трапитися.
MarcH

160

Щоб отримати підпроцес вихідний рядок за рядком, як тільки підпроцес очистить свій буфер 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, який виводить підпроцеси bytestring як є; Python 3 використовує текстовий режим (вихід cmd декодується за допомогою locale.getpreferredencoding(False)кодування).


що означає b ''?
Аарон,

4
b''є bytes
літералом

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() може бути кращим зменшити максимальне використання пам'яті - кількість переданих йому даних є довільною, але виключення цього призводить до зчитування всього виводу конвеєра одночасно, що, мабуть, не бажано.


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().



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
завжди добре пояснити, що робить ваше рішення, щоб люди краще розуміли
DaFois,

2
Вам слід подумати про використання shlex.split(myCommand)замість myCommand.split(). Він також вшановує пробіли в цитованих аргументах.
ЮтаДжархед,

0

Додавання іншого рішення python3 з кількома невеликими змінами:

  1. Дозволяє вловити код виходу з процесу оболонки (мені не вдалося отримати код виходу під час використання withконструкції)
  2. Також труби виводяться в реальному часі
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)
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.