Resumo executivo (ou versão "tl; dr"): é fácil quando há no máximo um subprocess.PIPE, caso contrário, é difícil.
Talvez seja hora de explicar um pouco sobre como subprocess.Popenfunciona.
(Advertência: isso é para Python 2.x, embora 3.x seja semelhante; e eu sou bastante confuso com a variante do Windows. Entendo muito melhor o material POSIX.)
A Popenfunção precisa lidar com zero a três fluxos de E / S, um pouco simultaneamente. Estes são indicados stdine stdout, stderrcomo de costume.
Você pode fornecer:
None, indicando que você não deseja redirecionar o fluxo. Ele os herdará como de costume. Observe que, nos sistemas POSIX, pelo menos, isso não significa que ele usará o Python sys.stdout, apenas o stdout real do Python ; veja a demonstração no final.
- Um
intvalor Este é um descritor de arquivo "bruto" (pelo menos no POSIX). (Nota lateral: PIPEe STDOUTna verdade são ints internamente, mas são descritores "impossíveis", -1 e -2.)
- Um fluxo - realmente, qualquer objeto com um
filenométodo. Popenencontrará o descritor para esse fluxo, usando stream.fileno()e, em seguida, prossiga como para um intvalor.
subprocess.PIPE, indicando que o Python deve criar um canal.
subprocess.STDOUT( stderrapenas): diga ao Python para usar o mesmo descritor que para stdout. Isso só faz sentido se você forneceu um Nonevalor (não ) para stdout, e mesmo assim, é necessário apenas se você definir stdout=subprocess.PIPE. (Caso contrário, você pode apenas fornecer o mesmo argumento fornecido stdout, por exemplo Popen(..., stdout=stream, stderr=stream),.)
Os casos mais fáceis (sem tubos)
Se você não redirecionar nada (deixe todos os três como o Nonevalor padrão ou o fornecimento explícitos None), Pipeé fácil. Ele só precisa desativar o subprocesso e deixá-lo funcionar. Ou, se você redirecionar para um não PIPE- intou um fluxo fileno()- ainda é fácil, pois o sistema operacional faz todo o trabalho. O Python só precisa desativar o subprocesso, conectando seu stdin, stdout e / ou stderr aos descritores de arquivo fornecidos.
O caso ainda fácil: um tubo
Se você redirecionar apenas um fluxo, as Pipecoisas ainda serão fáceis. Vamos escolher um fluxo de cada vez e assistir.
Suponha que você queira fornecer alguns stdin, mas deixe stdoute stderrvá sem redirecionar ou vá para um descritor de arquivo. Como processo pai, seu programa Python simplesmente precisa usar write()para enviar dados pelo canal . Você pode fazer isso sozinho, por exemplo:
proc = subprocess.Popen(cmd, stdin=subprocess.PIPE)
proc.stdin.write('here, have some data\n') # etc
ou você pode passar os dados stdin para proc.communicate(), o que faz o stdin.writemostrado acima. Não há saída retornando, portanto, communicate()há apenas outro trabalho real: ele também fecha o cano para você. (Se você não ligar, proc.communicate()deverá ligar proc.stdin.close()para fechar o canal, para que o subprocesso saiba que não há mais dados chegando.)
Suponha que você deseja capturar stdout, mas licença stdine stderrsó. Novamente, é fácil: basta ligar proc.stdout.read()(ou equivalente) até que não haja mais saída. Como proc.stdout()é um fluxo de E / S normal do Python, você pode usar todas as construções normais, como:
for line in proc.stdout:
ou, novamente, você pode usar proc.communicate(), o que simplesmente faz isso read()por você.
Se você deseja capturar apenas stderr, funciona da mesma forma que com stdout.
Há mais um truque antes que as coisas fiquem difíceis. Suponha que você queira capturar stdoute também capture, stderrmas no mesmo canal que stdout:
proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
Neste caso, subprocess"fraudes"! Bem, ele tem que fazer isso, para que não seja realmente trapaça: ele inicia o subprocesso com seu stdout e seu stderr direcionados para o (único) descritor de pipe que retorna ao processo pai (Python). No lado pai, há novamente apenas um descritor de pipe único para ler a saída. Toda a saída "stderr" é exibida proc.stdoute, se você chamar proc.communicate(), o resultado do stderr (segundo valor na tupla) será None, não uma sequência.
Os casos difíceis: dois ou mais tubos
Todos os problemas surgem quando você deseja usar pelo menos dois tubos. De fato, o subprocesspróprio código tem este bit:
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:
Mas, infelizmente, aqui fizemos pelo menos dois e talvez três tubos diferentes, então os count(None)retornos são 1 ou 0. Devemos fazer as coisas da maneira mais difícil.
No Windows, isso costuma threading.Threadacumular resultados para self.stdoute self.stderr, e o thread pai fornece self.stdindados de entrada (e fecha o canal).
No POSIX, ele usa, pollse disponível, caso contrário select, para acumular saída e fornecer entrada stdin. Tudo isso é executado no processo / thread pai (único).
Threads ou poll / select são necessários aqui para evitar conflitos. Suponha, por exemplo, que redirecionemos todos os três fluxos para três canais separados. Suponha ainda que haja um pequeno limite para a quantidade de dados que podem ser inseridos em um tubo antes que o processo de gravação seja suspenso, aguardando que o processo de leitura "limpe" o tubo da outra extremidade. Vamos definir esse pequeno limite para um único byte, apenas para ilustração. (Na verdade, é assim que as coisas funcionam, exceto que o limite é muito maior que um byte.)
Se o processo pai (Python) tentar gravar vários bytes - digamos, 'go\n'para proc.stdin, o primeiro byte entra e o segundo faz com que o processo Python seja suspenso, aguardando o subprocesso ler o primeiro byte, esvaziando o canal.
Enquanto isso, suponha que o subprocesso decida imprimir um amigável "Olá! Não entre em pânico!" cumprimento. Ele Hentra no tubo stdout, mas efaz com que seja suspenso, esperando que seu pai leia isso H, esvaziando o tubo stdout.
Agora estamos paralisados: o processo Python está adormecido, esperando para terminar de dizer "vá" e o subprocesso também está adormecido, esperando para terminar de dizer "Olá! Não entre em pânico!".
O subprocess.Popencódigo evita esse problema com threading-or-select / poll. Quando os bytes podem passar por cima dos tubos, eles vão. Quando não podem, apenas um segmento (não todo o processo) precisa dormir - ou, no caso de seleção / pesquisa, o processo Python espera simultaneamente por "pode escrever" ou "dados disponíveis", grava no stdin do processo somente quando houver espaço e lê seu stdout e / ou stderr somente quando os dados estiverem prontos. O proc.communicate()código (na verdade, _communicateonde os casos cabeludos são tratados) retorna uma vez que todos os dados stdin (se houver) foram enviados e todos os dados stdout e / ou stderr foram acumulados.
Se você quiser ler os dois stdoute stderrem dois canais diferentes (independentemente de qualquer stdinredirecionamento), também precisará evitar o conflito. O cenário de impasse aqui é diferente - ocorre quando o subprocesso grava stderralgum tempo enquanto você extrai dados stdoutou vice-versa - mas ainda está lá.
The Demo
Prometi demonstrar que, não redirecionado, o Python subprocesses grava no stdout subjacente, não sys.stdout. Então, aqui está um código:
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()
Quando executado:
$ python out.py
start show1
hello
in buffer: sys.stdout being buffered
start show2
hello
Observe que a primeira rotina falhará se você adicionar stdout=sys.stdout, pois um StringIOobjeto não possui fileno. O segundo omitirá o hellose você adicionar stdout=sys.stdoutuma vez sys.stdoutque foi redirecionado para os.devnull.
(Se você redirecionar de Python arquivo descritor de-1, o sub-processo vai seguir esse redirecionamento. A open(os.devnull, 'w')chamada produz um fluxo cujo fileno()é maior do que 2.)
Popen.pollcomo em uma pergunta anterior do Stack Overflow .