2009-07-24 2 views
26

У меня есть код Python, который выполняет внешнее приложение, которое отлично работает, когда приложение имеет небольшой объем вывода, но зависает, когда есть много. Мой код выглядит так:Использование subprocess.Popen для процесса с большим выходом

p = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) 
errcode = p.wait() 
retval = p.stdout.read() 
errmess = p.stderr.read() 
if errcode: 
    log.error('cmd failed <%s>: %s' % (errcode,errmess)) 

В документах есть комментарии, которые, как представляется, указывают на потенциальную проблему. Под засаде, есть:

Предупреждение: Это тупик, если дочерний процесс генерирует достаточно вывод на stdout или stderr трубы таким образом, что он блокирует ожидание буфера трубы OS принять больше данных. Используйте communicate(), чтобы этого избежать.

хотя при общении, я вижу:

Примечание чтения данных в буфер в памяти, поэтому не используйте этот метод, если размер данных является большим или неограниченным.

Так что мне непонятно, что я должен использовать любой из них, если у меня есть большой объем данных. Они не указывают, какой метод я должен использовать в этом случае.

Мне нужно вернуть значение из exec и выполнить разбор и использовать как stdout, так и stderr.

Итак, что такое эквивалентный метод в Python для выполнения внешнего приложения, которое будет иметь большой выход?

+3

кажется «большой» в сообщаться документация * гораздо больше *, чем вы, вероятно, ожидали, и безусловно, намного больше обычного. Например, вы можете вывести 10 МБ текста, и большинство систем будет в порядке с общением. Выход 1 ГБ, когда у вас всего 1 ГБ ОЗУ, будет другой историей. – 2010-02-26 04:29:01

ответ

16

Вы делаете блокирование читает два файла; первый должен завершиться до начала второго запуска. Если приложение много записывает в stderr, и ничего не делает stdout, тогда ваш процесс будет сидеть в ожидании данных на stdout, который не наступает, а программа, в которой вы работаете, ждет там, где она написана, stderr (чего никогда не будет - так как вы ждете stdout).

Есть несколько способов исправить это.

Проще всего не перехватывать stderr; оставить stderr=None. Ошибки будут выводиться непосредственно на stderr. Вы не можете перехватить их и отобразить их как часть своего собственного сообщения. Для инструментов командной строки это часто бывает нормально. Для других приложений это может быть проблемой.

Другим простым подходом является перенаправление stderr на stdout, поэтому у вас есть только один входящий файл: set stderr=STDOUT. Это означает, что вы не можете отличить регулярный вывод от вывода ошибки. Это может быть или не быть приемлемым, в зависимости от того, как приложение записывает вывод.

Полный и сложный способ обработки: select (http://docs.python.org/library/select.html). Это позволяет вам читать неблокируемым способом: вы получаете данные всякий раз, когда данные отображаются либо stdout, либо stderr. Я бы рекомендовал это только в том случае, если это действительно необходимо.Вероятно, это не работает в Windows.

+0

Поскольку конкретный случай, с которым я имею дело, будет иметь большое количество stdout и небольшую сумму или вообще не stderr, я сначала попробую перенаправить файл Mark, но более полное освещение проблемы очень полезно. – Tim

6

Много результатов является субъективным, поэтому немного сложно сделать рекомендацию. Если объем вывода действительно большой, то вы, скорее всего, не захотите взять все это с помощью одного вызова read(). Вы можете попробовать записать вывод в файл, а затем извлекать данные в пошагово, как, например:

f=file('data.out','w') 
p = subprocess.Popen(cmd, shell=True, stdout=f, stderr=subprocess.PIPE) 
errcode = p.wait() 
f.close() 
if errcode: 
    errmess = p.stderr.read() 
    log.error('cmd failed <%s>: %s' % (errcode,errmess)) 
for line in file('data.out'): 
    #do something 
+2

Это также может быть легко взаимоблокировка. Если разветвленный процесс записывает больше данных, чем ОС будет буферизовать до stderr перед выходом с кодом ошибки, этот код будет сидеть навсегда, ожидая его выхода, тогда как процесс сидит на блокирующей записи в stderr, ожидая, когда вы его прочитаете. –

+0

1) предполагает, что большой вывод данных - stderr, который был бы нечетным, но не неслыханным), 2) если stderr IS является источником большой суммы данных, то решение будет таким же, сделайте stderr файл также –

+0

В этом случае процесс может потенциально имеют большое количество stdout, но не будут иметь много, если они есть, stderr, поэтому это разумное решение для меня. – Tim

2

Вы могли бы попытаться связаться и посмотреть, решит ли это вашу проблему. Если нет, я перенаправляю вывод во временный файл.

+0

Поскольку сообщение явно предупреждает об отказе от использования, если у вас большой выход, я рассмотрю другие варианты. – Tim

6

Гленн Мейнард прав в своих комментариях о взаимоблокировках. Однако наилучшим способом решения этой проблемы является создание двух потоков: одно для stdout и одно для stderr, которое считывает эти соответствующие потоки до исчерпания и делает все, что вам нужно, с выходом.

Предложение использования временных файлов может работать или не работать для вас в зависимости от размера вывода и т. Д., И нужно ли обрабатывать вывод подпроцесса по мере его создания.

Как предложил Хейкки Тойвонен, вы должны посмотреть на метод communicate. Тем не менее, это буферирует stdout/stderr подпроцесса в памяти, и вы получаете возвращаемые из вызова communicate - это не идеально подходит для некоторых сценариев. Но стоит обратить внимание на источник метода связи.

Другой пример находится в пакете я утверждаю, python-gnupg, где gpg исполняемый породившей через subprocess делать тяжелую работу, и Python обертка нерестится темы, читать стандартный вывод компании GPG и поток ошибок и потреблять их в качестве данных производится GPG , Вы можете получить некоторые идеи, посмотрев на источник там. Данные, полученные gpg как для stdout, так и для stderr, могут быть довольно большими, в общем случае.

+0

В качестве примера рассмотрим python-gnupg. Благодарю. – Tim

+1

Релевантные ссылки на интересные методы - ['_open_subprocess'] (https://bitbucket.org/vinay.sajip/python-gnupg/src/952281d4c966608403a23af76429f11df9e0a852/gnupg.py?at=default&fileviewer=file-view-default#gnupg. py-825) и ['_collect_output'] (https://bitbucket.org/vinay.sajip/python-gnupg/src/952281d4c966608403a23af76429f11df9e0a852/gnupg.py?at=default&fileviewer=file-view-default#gnupg.py-903) – neowulf33

1

У меня была та же проблема. Если вам приходится обрабатывать большой вывод, другой хорошей возможностью может быть использование файла для stdout и stderr и передача этих файлов для каждого параметра.

Проверьте модуль tempfile в python: https://docs.python.org/2/library/tempfile.html.

Что-то подобное может работать

out = tempfile.NamedTemporaryFile(delete=False) 

Тогда вы могли бы сделать:

Popen(... stdout=out,...) 

Затем вы можете прочитать файл и удалить его позже.

5

Чтение stdout и stderr независимо друг от друга с очень большим выходом (т.е. много мегабайт) с использованием select:

import subprocess, select 

proc = subprocess.Popen(cmd, bufsize=8192, shell=False, \ 
    stdout=subprocess.PIPE, stderr=subprocess.PIPE) 

with open(outpath, "wb") as outf: 
    dataend = False 
    while (proc.returncode is None) or (not dataend): 
     proc.poll() 
     dataend = False 

     ready = select.select([proc.stdout, proc.stderr], [], [], 1.0) 

     if proc.stderr in ready[0]: 
      data = proc.stderr.read(1024) 
      if len(data) > 0: 
       handle_stderr_data(data) 

     if proc.stdout in ready[0]: 
      data = proc.stdout.read(1024) 
      if len(data) == 0: # Read of zero bytes means EOF 
       dataend = True 
      else: 
       outf.write(data) 
+0

Это, безусловно, имеет для меня наибольший смысл в преодолении проблем с памятью в памяти. Я даже пробовал подпроцесс 'cmd' как' bash -c 'cat/dev/urandom | tr -dc' a-zA-Z0-9 '"' который отлично работает. Мой ментальный блок был вокруг того, что означают эти строки - [1] 'ready [0]' и почему [2] 'len (proc.stdout.read (1024)) == 0' означает EOF? [3] Почему бы не проверить 'len (proc.stderr.read (1024))'? [4] Почему чтение не нужно? Извините, несколько вопросов собраны в один комментарий:/ – neowulf33

+1

@ neowulf33 [1] ready - список списков, готовый [0] - это список, который может содержать либо stdout, stderr, либо и то, и другое. см. раздел Выбор документов. [2] «Пустая строка возвращается, когда EOF встречается немедленно». https://docs.python.org/2.7/library/stdtypes.html#file.read [3], потому что вы потеряете данные! [4] Я не понимаю, как флеш? – vz0

+0

Спасибо! Мой плохой - я был определенно спящим, когда писал «читать флеш»! – neowulf33

Смежные вопросы