читати підпроцесовий вигляд рядка за рядком


235

Мій скрипт python використовує підпроцес для виклику утиліти Linux, яка дуже шумно. Я хочу зберегти весь результат у файлі журналу та показати його частині користувачеві. Я думав, що наступне спрацює, але результат не відображається в моїй програмі, поки утиліта не виробить значну кількість результатів.

#fake_utility.py, just generates lots of output over time
import time
i = 0
while True:
   print hex(i)*512
   i += 1
   time.sleep(0.5)

#filters output
import subprocess
proc = subprocess.Popen(['python','fake_utility.py'],stdout=subprocess.PIPE)
for line in proc.stdout:
   #the real code does filtering here
   print "test:", line.rstrip()

Мені дуже хочеться, щоб сценарій фільтра друкував кожен рядок у міру отримання підпроцесу. Сорта подобається, що teeробить, але з кодом python.

Що я пропускаю? Це навіть можливо?


Оновлення:

Якщо a sys.stdout.flush()додано до fake_utility.py, код має бажану поведінку в python 3.1. Я використовую python 2.6. Ви можете подумати, що використання proc.stdout.xreadlines()буде працювати так само, як py3k, але це не так.


Оновлення 2:

Ось мінімальний робочий код.

#fake_utility.py, just generates lots of output over time
import sys, time
for i in range(10):
   print i
   sys.stdout.flush()
   time.sleep(0.5)

#display out put line by line
import subprocess
proc = subprocess.Popen(['python','fake_utility.py'],stdout=subprocess.PIPE)
#works in python 3.0+
#for line in proc.stdout:
for line in iter(proc.stdout.readline,''):
   print line.rstrip()

4
ви можете використовувати print line,замість print line.rstrip()(примітка: кома в кінці).
jfs


2
Оновлення 2 зазначає, що воно працює з python 3.0+, але використовує старий оператор друку, тому він не працює з python 3.0+.
Rooky

Жодна з перелічених тут відповідей не працювала для мене, але stackoverflow.com/questions/5411780/… зробив!
бокс

Відповіді:


179

Минуло довгий час, коли я востаннє працював з Python, але я думаю, що проблема полягає у тому твердженні for line in proc.stdout, яке читає весь вхід, перш ніж його повторити. Рішення полягає в тому, щоб використовувати readline()замість цього:

#filters output
import subprocess
proc = subprocess.Popen(['python','fake_utility.py'],stdout=subprocess.PIPE)
while True:
  line = proc.stdout.readline()
  if not line:
    break
  #the real code does filtering here
  print "test:", line.rstrip()

Звичайно, вам все одно доведеться мати справу з буферизацією підпроцесу.

Примітка: згідно з документацією рішення з ітератором має бути рівнозначним використанню readline(), за винятком буфера попереднього читання, але (або саме через це) запропонована зміна дала для мене різні результати (Python 2.5 для Windows XP).


11
для file.readline()порівняння for line in fileдив. bugs.python.org/issue3907 (коротше: він працює на Python3; використовувати io.open()на Python 2.6+)
jfs

5
Більш пітонічний тест для EOF, згідно з "Рекомендаціями щодо програмування" в PEP 8 ( python.org/dev/peps/pep-0008 ), буде "якби не рядок:".
Джейсон Мок

14
@naxa: для труб : for line in iter(proc.stdout.readline, ''):.
jfs

3
@ Jan-PhilipGehrcke: так. 1. ви могли використовувати for line in proc.stdoutна Python 3 (немає помилки зчитування вперед) 2. '' != b''на Python 3 - не копіюйте і вставте код наосліп - подумайте, що він робить і як це працює.
jfs

2
@JFSebastian: звичайно, iter(f.readline, b'')рішення є досить очевидним (а також працює на Python 2, якщо когось цікавить). Суть мого коментаря полягала не в тому, щоб звинувачувати ваше рішення (вибачте, якщо воно виглядало так, я читав, що зараз теж!), А в тому, щоб описати ступінь симптомів, які в цьому випадку є досить серйозними (більшість Py2 / 3 питання призводять до винятків, тоді як тут добре сприйнятий цикл змінився на нескінченний, а збирання сміття веде боротьбу з потопом новостворених об'єктів, спричиняючи коливання пам’яті з тривалим періодом та великою амплітудою).
Доктор Ян-Філіп Герк

45

Трохи запізнився на вечірку, але здивувався, не побачивши, що, на мою думку, тут є найпростішим рішенням:

import io
import subprocess

proc = subprocess.Popen(["prog", "arg"], stdout=subprocess.PIPE)
for line in io.TextIOWrapper(proc.stdout, encoding="utf-8"):  # or another encoding
    # do something with line

(Для цього потрібен Python 3.)


25
Я хотів би скористатися цією відповіддю, але я отримую: AttributeError: 'file' object has no attribute 'readable' py2.7
Dan Garthwaite

3
Працює з python 3
matanster

Очевидно, що цей код не дійсний з декількох причин сумісності py3 / py3 та реального ризику отримати ValueError: Операція вводу / виводу на закритому файлі
sorin

3
@sorin жодна з цих речей не робить його "недійсним". Якщо ви пишете бібліотеку, яка все ще потребує підтримки Python 2, не використовуйте цей код. Але багатьом людям властиво використовувати програмне забезпечення, випущене недавно десятиліття тому. Якщо ви спробуєте прочитати у закритому файлі, ви отримаєте це виключення незалежно від того, використовуєте ви TextIOWrapperчи ні. Ви можете просто обробити виняток.
jbg

1
Ви, можливо, запізнюєтесь на вечірку, але ви відповідаєте актуальною для поточної версії Python, ty
Душан Глігорік

20

Дійсно, якщо ви відібрали ітератор, то буферизація тепер може стати вашою проблемою. Ви можете сказати python в підпроцесі не буферувати його вихід.

proc = subprocess.Popen(['python','fake_utility.py'],stdout=subprocess.PIPE)

стає

proc = subprocess.Popen(['python','-u', 'fake_utility.py'],stdout=subprocess.PIPE)

Мені це було потрібно при виклику python зсередини python.


14

Ці додаткові параметри потрібно передати subprocess.Popen:

bufsize=1, universal_newlines=True

Тоді ви можете повторити, як у вашому прикладі. (Тестовано з Python 3.5)


2
@nicoulaj Це має працювати, якщо використовується пакет subprocess32.
Quantum7

4

Функція, яка дозволяє повторювати stdoutі stderrодночасно, і в реальному часі, рядок за рядком

У випадку, якщо вам потрібно отримати вихідний потік для обох stdoutі stderrодночасно, ви можете скористатися наступною функцією.

Функція використовує черги для об'єднання обох труб Popen в один ітератор.

Тут ми створюємо функцію read_popen_pipes():

from queue import Queue, Empty
from concurrent.futures import ThreadPoolExecutor


def enqueue_output(file, queue):
    for line in iter(file.readline, ''):
        queue.put(line)
    file.close()


def read_popen_pipes(p):

    with ThreadPoolExecutor(2) as pool:
        q_stdout, q_stderr = Queue(), Queue()

        pool.submit(enqueue_output, p.stdout, q_stdout)
        pool.submit(enqueue_output, p.stderr, q_stderr)

        while True:

            if p.poll() is not None and q_stdout.empty() and q_stderr.empty():
                break

            out_line = err_line = ''

            try:
                out_line = q_stdout.get_nowait()
            except Empty:
                pass
            try:
                err_line = q_stderr.get_nowait()
            except Empty:
                pass

            yield (out_line, err_line)

read_popen_pipes() у вживанні:

import subprocess as sp


with sp.Popen(my_cmd, stdout=sp.PIPE, stderr=sp.PIPE, text=True) as p:

    for out_line, err_line in read_popen_pipes(p):

        # Do stuff with each line, e.g.:
        print(out_line, end='')
        print(err_line, end='')

    return p.poll() # return status-code

2

Ви також можете читати рядки без циклу. Працює в python3.6.

import os
import subprocess

process = subprocess.Popen(command, stdout=subprocess.PIPE)
list_of_byte_strings = process.stdout.readlines()

1
Або перетворити на рядки:list_of_strings = [x.decode('utf-8').rstrip('\n') for x in iter(process.stdout.readlines())]
ndtreviv

1

Я спробував це з python3, і воно спрацювало, джерело

def output_reader(proc):
    for line in iter(proc.stdout.readline, b''):
        print('got line: {0}'.format(line.decode('utf-8')), end='')


def main():
    proc = subprocess.Popen(['python', 'fake_utility.py'],
                            stdout=subprocess.PIPE,
                            stderr=subprocess.STDOUT)

    t = threading.Thread(target=output_reader, args=(proc,))
    t.start()

    try:
        time.sleep(0.2)
        import time
        i = 0

        while True:
        print (hex(i)*512)
        i += 1
        time.sleep(0.5)
    finally:
        proc.terminate()
        try:
            proc.wait(timeout=0.2)
            print('== subprocess exited with rc =', proc.returncode)
        except subprocess.TimeoutExpired:
            print('subprocess did not terminate in time')
    t.join()

1

На Python 2 і 3 (2.7.12 і 3.6.1) для мене працює наступна модифікація відповіді Ромуло:

import os
import subprocess

process = subprocess.Popen(command, stdout=subprocess.PIPE)
while True:
  line = process.stdout.readline()
  if line != '':
    os.write(1, line)
  else:
    break

0

Не знаю, коли це додано в модуль підпроцесу, але з Python 3 вам слід добре proc.stdout.splitlines():

for line in proc.stdout.splitlines():
   print "stdout:", line
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.