объединение нескольких подпроцессов

У меня есть 5 процессов p1,p2,...,p5, в которых я хочу записать некоторые данные на стандартный ввод p1, передать вывод p1 на стандартный ввод p2 и, наконец, прочитать окончательный результат с вывода p5.

Что я пробовал до сих пор:

p1 = Popen(['p1'], stdin=PIPE, stdout=PIPE)
p2 = Popen(['p2'], stdin=p1.stdout, stdout=PIPE)
...
p5 = Popen(['p5'], stdin=p4.stdout, stdout=PIPE)

# write data to stdin
p1.stdin.write(indata)
p1.stdin.close()

# not sure in what order to close the pipes here, if at all

# read output
out = p5.stdout.read()
print out

Последний отрезанный код просто зависает, потому что я, должно быть, неправильно выполняю операции чтения/записи.

Мне удалось получить работающие отдельные процессы, используя communicate() и два процесса, не вводя никаких входных данных для первого (пример из документации Python):

output=`dmesg | grep hda`
==>
p1 = Popen(["dmesg"], stdout=PIPE)
p2 = Popen(["grep", "hda"], stdin=p1.stdout, stdout=PIPE)
p1.stdout.close()  # Allow p1 to receive a SIGPIPE if p2 exits.
output = p2.communicate()[0]

Но я не могу понять, как ввести данные в первый процесс, не зависая интерпретатор.

Я также мог бы использовать для этого сценарий bash (который я уже написал и работает), но я хотел бы знать, как добиться того же с помощью Python.

Итак, я хотел бы спросить, как все это сделать правильно, в частности, в каком порядке выполнять операции чтения/записи/закрытия каналов?

Я работаю над 64-битным Linux, если это имеет значение.

РЕДАКТИРОВАТЬ: я забыл упомянуть, что все процессы p1,..p5 потребляют все вводимые данные, обрабатывают их, записывают в стандартный вывод и затем завершают работу. Таким образом, процессы, следующие в конвейере, не должны завершаться до завершения обработки предыдущих.

EDIT2: я знаю, что также мог бы использовать

command = 'bash -c "p1 | p2 | p3 | p4 | p5"'
proc = Popen([command], shell=True)
out, err = proc.communicate(input=indata)
print out

но мой главный интерес - узнать, как связать каналы исключительно в коде Python.


person Timo    schedule 14.06.2011    source источник
comment
Здесь есть связанный с этим вопрос: stackoverflow.com/q/295459/1858225 Похоже, что использование явного временного файла (как в принятый ответ здесь) не необходим; однако, похоже, не существует простого и чисто питонического способа сделать это, что меня удивляет. Plumbum (упомянутый в одном из ответов) выглядит довольно красиво, но для меня слишком волшебно (это Python, а не Perl!). Методы для достижения этого полностью с использованием subprocess (например, sam.nipl.net/code/python/ pipe.py из комментария к другому ответу), по-видимому, подвержены странным ошибкам.   -  person Kyle Strand    schedule 04.12.2014
comment
.... на самом деле, я только что обнаружил модуль pipes (docs.python.org/2 /library/pipes.html) и соответственно добавил ответ на другой вопрос. Это выглядит намного лучше, чем другие решения.   -  person Kyle Strand    schedule 04.12.2014


Ответы (1)


Может быть, это может помочь:

import sys
import tempfile
from subprocess import Popen, PIPE


cmd = [sys.executable, '-c', 'print raw_input()']

# Using a temp file to give input data to the subprocess instead of stdin.write to avoid deadlocks.
with tempfile.TemporaryFile() as f:
    f.write('foobar')
    f.seek(0)  # Return at the start of the file so that the subprocess p1 can read what we wrote.
    p1 = Popen(cmd, stdin=f, stdout=PIPE)

p2 = Popen(cmd, stdin=p1.stdout, stdout=PIPE)
p3 = Popen(cmd, stdin=p2.stdout, stdout=PIPE)

# No order needed.
p1.stdout.close()
p2.stdout.close()

# Using communicate() instead of stdout.read to avoid deadlocks. 
print p3.communicate()[0]

Выход:

$ python test.py
foobar

Надеюсь, что это может быть hepfull.

person mouad    schedule 14.06.2011
comment
Спасибо за ваше решение. Это очень умно и работает. Как я понимаю, без реальных файловых дескрипторов нельзя сделать ввод? Например, использование файловых объектов StringIO не работает, потому что нет fileno? - person Timo; 14.06.2011
comment
@Timo: Да, это правда, вам нужен настоящий файл с fileno, и рад, что это было полезно :) - person mouad; 14.06.2011
comment
Хех, как только я прочитал вопрос, я подумал, что уверен, что ответ каким-то образом будет включать файловый ввод-вывод... - person JAB; 14.06.2011
comment
@JAB: Да, модуль подпроцесса не дает нам большого выбора :) - person mouad; 14.06.2011
comment
Примечание Python 3: print(input()) и f.write(b'foobar') (или with tempfile.TemporaryFile('w')). Тем не менее, я не мог проверить, работает ли он полностью или нет в моем сценарии, так как в моем случае я устанавливал значения конфигурации в Ubuntu с cmd=["gsettings", "set", gsettings_schema, gsettings_key, value] дважды подряд, но окончательное значение иногда было 1-м, иногда 2-м. - person hsandt; 26.08.2017