Резюме (или версия "tl; dr"): легко, когда есть не более одного subprocess.PIPE
, иначе сложно.
Может быть, пришло время объяснить немного о том, как subprocess.Popen
работает.
(Предостережение: это для Python 2.x, хотя 3.x похож; и я довольно размышляю над вариантом Windows. Я понимаю, что такое POSIX гораздо лучше.)
Popen
Функция должна иметь дело с нулевой до трех ввода / вывода потоков, несколько одновременно. Они обозначены stdin
, stdout
и , stderr
как обычно.
Вы можете предоставить:
None
, указывая, что вы не хотите перенаправлять поток. Вместо этого он унаследует их как обычно. Обратите внимание, что в системах POSIX, по крайней мере, это не означает, что он будет использовать Python sys.stdout
, просто фактический вывод Python ; см. демонстрацию в конце.
int
Значение. Это «сырой» файловый дескриптор (по крайней мере, в POSIX). (Примечание: PIPE
и STDOUT
на самом деле они хранятся int
внутри, но являются «невозможными» дескрипторами, -1 и -2.)
- Поток - действительно, любой объект с
fileno
методом. Popen
найдет дескриптор для этого потока, используя stream.fileno()
, а затем продолжите как дляint
значения.
subprocess.PIPE
, указывая, что Python должен создать канал.
subprocess.STDOUT
( stderr
только для ): указать Python использовать тот же дескриптор, что и для stdout
. Это имеет смысл только в том случае, если вы указали (не None
) значение для stdout
, и даже тогда оно будет необходимо, только если вы установили stdout=subprocess.PIPE
. (В противном случае вы можете просто указать тот же аргумент, который вы указали stdout
, например,. Popen(..., stdout=stream, stderr=stream)
)
Самые простые случаи (без труб)
Если вы ничего не перенаправляете (оставьте все три в качестве значения по умолчанию None
или укажите явное значение None
), сделать Pipe
это довольно просто. Нужно просто раскрутить подпроцесс и запустить его. Или, если вы перенаправляете на не PIPE
- int
или поток fileno()
- все еще легко, так как ОС делает всю работу. Python просто должен раскрутить подпроцесс, соединив его stdin, stdout и / или stderr с предоставленными файловыми дескрипторами.
Все еще легкий случай: одна труба
Если вы перенаправите только один поток, Pipe
все еще будет довольно просто. Давайте выберем один поток за раз и посмотрим.
Предположим, что вы хотите предоставить некоторые из них stdin
, но отпустите stdout
и stderr
не будете перенаправлены, или перейдите к дескриптору файла. Как родительский процесс, ваша программа на Python просто нуждается write()
в передаче данных по конвейеру. Вы можете сделать это самостоятельно, например:
proc = subprocess.Popen(cmd, stdin=subprocess.PIPE)
proc.stdin.write('here, have some data\n') # etc
или вы можете передать данные stdin proc.communicate()
, что затем и делает, как stdin.write
показано выше. Нет возвращаемого результата, поэтому communicate()
есть только одна реальная работа: он также закрывает канал для вас. (Если вы не звоните, proc.communicate()
вы должны позвонить, proc.stdin.close()
чтобы закрыть канал, чтобы подпроцесс знал, что данные больше не поступают.)
Предположим, вы хотите захватить, stdout
но оставить stdin
и в stderr
покое. Опять же, это просто: просто позвоните proc.stdout.read()
(или эквивалент), пока не закончится вывод. Так proc.stdout()
как это обычный поток ввода / вывода Python, вы можете использовать все обычные конструкции, например:
for line in proc.stdout:
или, опять же, вы можете использовать proc.communicate()
, который просто делает read()
для вас.
Если вы хотите захватить только stderr
, он работает так же, как с stdout
.
Есть еще один трюк, прежде чем все станет сложно. Предположим, вы хотите захватить stdout
, а также захватить, stderr
но на том же канале, что и стандартный вывод:
proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
В этом случае subprocess
«читы»! Что ж, он должен это сделать, так что это на самом деле не обман: он запускает подпроцесс как со своим stdout, так и со своим stderr, направленным в (единственный) дескриптор канала, который возвращается обратно в свой родительский (Python) процесс. На родительской стороне снова есть только один дескриптор канала для чтения вывода. Весь вывод "stderr" отображается в proc.stdout
, и если вы вызываете proc.communicate()
, результат stderr (второе значение в кортеже) будет None
, а не строка.
Трудные случаи: две или более труб
Все проблемы возникают, когда вы хотите использовать как минимум две трубы. На самом деле, сам subprocess
код имеет этот бит:
def communicate(self, input=None):
...
# Optimization: If we are only using one pipe, or no pipe at
# all, using select() or threads is unnecessary.
if [self.stdin, self.stdout, self.stderr].count(None) >= 2:
Но, увы, здесь мы сделали, по крайней мере, две, а может и три, разные трубы, так что count(None)
возвращает либо 1, либо 0. Мы должны делать что-то сложное.
В Windows это используется threading.Thread
для накопления результатов для self.stdout
и self.stderr
, и родительский поток доставляет self.stdin
входные данные (а затем закрывает канал).
В POSIX это используется, poll
если доступно, в противном случае select
, чтобы накапливать выходные данные и доставлять ввод стандартного ввода. Все это выполняется в (единственном) родительском процессе / потоке.
Здесь нужны темы или опрос / выборка, чтобы избежать тупика. Предположим, например, что мы перенаправили все три потока на три отдельных канала. Предположим далее, что существует небольшое ограничение на то, сколько данных можно вставить в канал, прежде чем процесс записи будет приостановлен, ожидая, пока процесс чтения «очистит» канал от другого конца. Давайте установим это небольшое ограничение в один байт, просто для иллюстрации. (Это на самом деле, как все работает, за исключением того, что предел намного больше, чем один байт.)
Если родительский (Python) процесс пытается записать несколько байтов, скажем, 'go\n'
в proc.stdin
, первый байт входит, а затем второй вызывает приостановку процесса Python, ожидая, пока подпроцесс прочитает первый байт, опустошив канал.
Между тем, предположим, что подпроцесс решает напечатать дружественное «Привет! Не паникуйте!» приветствие. The H
отправляется в свой канал stdout, но e
вызывает его приостановку, ожидая, когда его родитель прочитает это H
, освобождая канал stdout.
Теперь мы застряли: процесс Python спит, ожидая, чтобы закончить, говоря «go», и подпроцесс также спит, ожидая, чтобы закончить, говоря «Hello! Don't Panic!».
subprocess.Popen
Код устраняет эту проблему с резьбонарезным или -выбором / опросом. Когда байты могут пройти по каналам, они идут. Когда они не могут, только поток (не весь процесс) должен спать - или, в случае выбора / опроса, процесс Python ожидает одновременно "может записать" или "данные доступны", записывает в stdin процесса только когда есть место, и читает свои stdout и / или stderr только тогда, когда данные готовы. proc.communicate()
Код ( на самом деле , _communicate
где волосатые случаи обрабатываются) возвращает сразу все данные стандартного устройства ввода (если таковые имеются) были посланы и все данные STDOUT и / или STDERR накоплено.
Если вы хотите читать как по двум, так stdout
и stderr
по двум разным каналам (независимо от stdin
перенаправления), вам также нужно избегать тупиковых ситуаций. Сценарий взаимоблокировки здесь другой - он возникает, когда подпроцесс записывает что-то долгое stderr
время, пока вы извлекаете данные stdout
, или наоборот, - но он все еще там.
Демо
Я обещал продемонстрировать, что не перенаправленные Python subprocess
пишут в стандартный вывод, а не sys.stdout
. Итак, вот код:
from cStringIO import StringIO
import os
import subprocess
import sys
def show1():
print 'start show1'
save = sys.stdout
sys.stdout = StringIO()
print 'sys.stdout being buffered'
proc = subprocess.Popen(['echo', 'hello'])
proc.wait()
in_stdout = sys.stdout.getvalue()
sys.stdout = save
print 'in buffer:', in_stdout
def show2():
print 'start show2'
save = sys.stdout
sys.stdout = open(os.devnull, 'w')
print 'after redirect sys.stdout'
proc = subprocess.Popen(['echo', 'hello'])
proc.wait()
sys.stdout = save
show1()
show2()
Когда запустить:
$ python out.py
start show1
hello
in buffer: sys.stdout being buffered
start show2
hello
Обратите внимание, что первая подпрограмма потерпит неудачу, если вы добавите stdout=sys.stdout
, так как StringIO
объект не имеет fileno
. Второй пропустит, hello
если вы добавите, stdout=sys.stdout
так sys.stdout
как был перенаправлен на os.devnull
.
(Если вы перенаправите Python file-descriptor-1, подпроцесс будет следовать за этим перенаправлением. При open(os.devnull, 'w')
вызове создается поток, значение которого fileno()
больше 2.)
Popen.poll
как в предыдущем вопросе переполнения стека .