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.Popen
funciona.
(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 Popen
função precisa lidar com zero a três fluxos de E / S, um pouco simultaneamente. Estes são indicados stdin
e stdout
, stderr
como 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
int
valor Este é um descritor de arquivo "bruto" (pelo menos no POSIX). (Nota lateral: PIPE
e STDOUT
na verdade são int
s internamente, mas são descritores "impossíveis", -1 e -2.)
- Um fluxo - realmente, qualquer objeto com um
fileno
método. Popen
encontrará o descritor para esse fluxo, usando stream.fileno()
e, em seguida, prossiga como para um int
valor.
subprocess.PIPE
, indicando que o Python deve criar um canal.
subprocess.STDOUT
( stderr
apenas): diga ao Python para usar o mesmo descritor que para stdout
. Isso só faz sentido se você forneceu um None
valor (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 None
valor 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
- int
ou 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 Pipe
coisas ainda serão fáceis. Vamos escolher um fluxo de cada vez e assistir.
Suponha que você queira fornecer alguns stdin
, mas deixe stdout
e stderr
vá 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.write
mostrado 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 stdin
e stderr
só. 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 stdout
e também capture, stderr
mas 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.stdout
e, 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 subprocess
pró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.Thread
acumular resultados para self.stdout
e self.stderr
, e o thread pai fornece self.stdin
dados de entrada (e fecha o canal).
No POSIX, ele usa, poll
se 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 H
entra no tubo stdout, mas e
faz 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.Popen
có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, _communicate
onde 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 stdout
e stderr
em dois canais diferentes (independentemente de qualquer stdin
redirecionamento), também precisará evitar o conflito. O cenário de impasse aqui é diferente - ocorre quando o subprocesso grava stderr
algum tempo enquanto você extrai dados stdout
ou vice-versa - mas ainda está lá.
The Demo
Prometi demonstrar que, não redirecionado, o Python subprocess
es 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 StringIO
objeto não possui fileno
. O segundo omitirá o hello
se você adicionar stdout=sys.stdout
uma vez sys.stdout
que 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.poll
como em uma pergunta anterior do Stack Overflow .