Підсумок (або версія "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 subprocess
es пише в основний 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
як у попередньому запитанні про переповнення стека .