Підсумок (або версія "tl; dr"): це легко, коли існує максимум одна subprocess.PIPE, інакше важко.
Можливо, час трохи пояснити, як subprocess.Popenце відбувається.
(Caveat: це для 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але на тій же трубі, що і stdout:
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для накопичення виводу та доставки вводу stdin. Все це працює в (єдиному) батьківському процесі / потоці.
Тут потрібні нитки або опитування / вибір, щоб уникнути тупикової ситуації. Припустимо, наприклад, що ми перенаправили всі три потоки на три окремі труби. Припустимо також, що існує невелика межа щодо того, скільки даних можна вставити в трубу до того, як процес запису буде призупинено, чекаючи, коли процес читання "очистить" трубку з іншого кінця. Давайте встановимо цю невелику межу на один байт, просто для ілюстрації. (Це насправді, як все працює, за винятком того, що межа набагато перевищує один байт.)
Якщо батьківський (Python) процес намагається записати кілька байтів - скажімо, 'go\n'до proc.stdin, перший байт входить, а потім другий змушує призупинити процес Python, чекаючи, коли підпроцес прочитає перший байт, випорожнивши трубу.
Тим часом, припустимо, підпроцес вирішує надрукувати дружнє "Привіт! Не панікуй!" привітання. Він Hпотрапляє в свою трубку stdout, але eзмушує її призупинятись, чекаючи, коли батько прочитає це H, випорожнивши трубу stdout.
Зараз ми застрягли: процес Python спить, чекаючи, коли закінчиться сказати "йди", а підпроцес також спить, чекаючи, що закінчить "Привіт! Не панікуй!".
subprocess.PopenКод усуває цю проблему з різьбонарізного або -Вибір / опитуванням. Коли байти можуть перейти через труби, вони йдуть. Якщо вони не в змозі, повинен спати лише потік (не весь процес), або, у випадку вибору / опитування, процес Python чекає одночасно на "може записати" або "доступні дані", пише в stdin процесу лише тоді, коли є місце, і читає його stdout та / або stderr лише тоді, коли дані готові. proc.communicate()Код ( на насправді , _communicateде волохаті випадки обробляються) повертає відразу всі дані стандартного пристрою введення (якщо такі є) були послані і всі дані STDOUT і / або STDERR накопичено.
Якщо ви хочете прочитати як stdoutі stderrна двох різних трубах (незалежно від будь-якого stdinперенаправлення), вам також потрібно буде уникати тупикової ситуації. Сценарій тупикової ситуації тут інший - це відбувається, коли підпроцес записує щось довге, stderrпоки витягуєте дані stdout, або навпаки - але він все ще є.
Демо
Я пообіцяв продемонструвати, що, не перенаправлений, Python subprocesses пише в основний stdout, ні 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.
(Якщо ви перенаправляєте файл-дескриптор-1 Python, підпроцес буде слідувати за цим перенаправленням. open(os.devnull, 'w')Виклик створює потік, fileno()більший за 2.)
Popen.pollяк у попередньому запитанні про переповнення стека .