Quero subprocess.Popen()
rsync.exe no Windows e imprimir o stdout em Python.
Meu código funciona, mas não detecta o progresso até que uma transferência de arquivo seja concluída! Quero imprimir o progresso de cada arquivo em tempo real.
Usando Python 3.1 agora, desde que ouvi dizer que deve ser melhor para lidar com IO.
import subprocess, time, os, sys
cmd = "rsync.exe -vaz -P source/ dest/"
p, line = True, 'start'
p = subprocess.Popen(cmd,
shell=True,
bufsize=64,
stdin=subprocess.PIPE,
stderr=subprocess.PIPE,
stdout=subprocess.PIPE)
for line in p.stdout:
print(">>> " + str(line.rstrip()))
p.stdout.flush()
python
subprocess
stdout
John A
fonte
fonte
Respostas:
Algumas regras básicas para
subprocess
.shell=True
. Ele invoca desnecessariamente um processo shell extra para chamar seu programa.sys.argv
em python é uma lista, e tambémargv
em C. Então, você passa uma lista paraPopen
para chamar subprocessos, não uma string.stderr
para umPIPE
quando não estiver lendo.stdin
quando você não estiver escrevendo.Exemplo:
import subprocess, time, os, sys cmd = ["rsync.exe", "-vaz", "-P", "source/" ,"dest/"] p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) for line in iter(p.stdout.readline, b''): print(">>> " + line.rstrip())
Dito isso, é provável que o rsync armazene sua saída ao detectar que está conectado a um pipe em vez de um terminal. Este é o comportamento padrão - quando conectado a um pipe, os programas devem limpar explicitamente o stdout para resultados em tempo real, caso contrário, a biblioteca C padrão irá armazenar em buffer.
Para testar isso, tente executar o seguinte:
cmd = [sys.executable, 'test_out.py']
e crie um
test_out.py
arquivo com o conteúdo:import sys import time print ("Hello") sys.stdout.flush() time.sleep(10) print ("World")
A execução desse subprocesso deve dar a você "Hello" e esperar 10 segundos antes de dar "World". Se isso acontecer com o código Python acima e não com
rsync
, isso significa quersync
ele próprio está armazenando a saída em buffer, então você está sem sorte.Uma solução seria conectar-se diretamente a um
pty
, usando algo parecidopexpect
.fonte
shell=False
é a coisa certa quando você constrói uma linha de comando, especialmente a partir de dados inseridos pelo usuário. Mas, no entanto, tambémshell=True
é útil quando você obtém toda a linha de comando de uma fonte confiável (por exemplo, codificada no script).shell=True
. Pense nisso - você está invocando outro processo em seu sistema operacional, envolvendo alocação de memória, uso de disco, programação de processador, apenas para dividir uma string ! E um você se juntou !! Você pode dividir em python, mas é mais fácil escrever cada parâmetro separadamente de qualquer maneira. Além disso, usando uma lista significa que você não tem que escapar caracteres especiais do escudo: espaços,;
,>
,<
,&
.. seus parâmetros podem conter esses caracteres e você não precisa se preocupar! Não vejo razão para usarshell=True
, realmente, a menos que você esteja executando um comando somente shell.csv
módulo. Mas, por exemplo, seu pipeline em python seria:p = Popen(['cut', '-f1'], stdin=open('longfile.tab'), stdout=PIPE) ; p2 = Popen(['head', '-100'], stdin=p.stdout, stdout=PIPE) ; result, stderr = p2.communicate() ; print result
Observe que você pode trabalhar com nomes de arquivos longos e caracteres especiais de shell sem ter que escapar, agora que o shell não está envolvido. Além disso, é muito mais rápido, pois há um processo a menos.for line in iter(p.stdout.readline, b'')
vez defor line in p.stdout
no Python 2, caso contrário, as linhas não são lidas em tempo real, mesmo se o processo de origem não buffer sua saída.Eu sei que este é um assunto antigo, mas agora há uma solução. Chame o rsync com a opção --outbuf = L. Exemplo:
cmd=['rsync', '-arzv','--backup','--outbuf=L','source/','dest'] p = subprocess.Popen(cmd, stdout=subprocess.PIPE) for line in iter(p.stdout.readline, b''): print '>>> {}'.format(line.rstrip())
fonte
preexec_fn=os.setpgrp
para fazer o programa sobreviver ao script pai 2. você pular a leitura do canal do processo 3. o processo produzir muitos dados, preenchendo o tubo 4. você ficar preso por horas , tentando descobrir por que o programa que você está executando é encerrado após um período de tempo aleatório . A resposta de @nosklo me ajudou muito.No Linux, tive o mesmo problema para me livrar do buffer. Finalmente usei "stdbuf -o0" (ou, sem buffer de expect) para me livrar do buffer PIPE.
proc = Popen(['stdbuf', '-o0'] + cmd, stdout=PIPE, stderr=PIPE) stdout = proc.stdout
Eu poderia então usar select.select em stdout.
Veja também /unix/25372/
fonte
stdbuf -o0
provou ser realmente útil com um monte de testes pytest / pytest-bdd que escrevi para gerar um aplicativo C ++ e verificar se ele emite certas instruções de log. Semstdbuf -o0
, esses testes precisavam de 7 segundos para obter a saída (em buffer) do programa C ++. Agora eles funcionam quase que instantaneamente!Dependendo do caso de uso, você também pode desabilitar o armazenamento em buffer no próprio subprocesso.
Se o subprocesso for um processo Python, você pode fazer isso antes da chamada:
os.environ["PYTHONUNBUFFERED"] = "1"
Ou, alternativamente, passe isso no
env
argumento paraPopen
.Caso contrário, se você estiver no Linux / Unix, pode usar a
stdbuf
ferramenta. Por exemplo, como:cmd = ["stdbuf", "-oL"] + cmd
Veja também aqui sobre
stdbuf
ou outras opções.fonte
for line in p.stdout: ...
sempre bloqueia até o próximo avanço de linha.
Para comportamento em "tempo real", você deve fazer algo assim:
while True: inchar = p.stdout.read(1) if inchar: #neither empty string nor None print(str(inchar), end='') #or end=None to flush immediately else: print('') #flush for implicit line-buffering break
O loop while é deixado quando o processo filho fecha seu stdout ou sai.
read()/read(-1)
bloquearia até que o processo filho fechasse seu stdout ou saísse.fonte
inchar
nunca éNone
usadoif not inchar:
(read()
retorna uma string vazia em EOF). a propósito, é piorfor line in p.stdout
não imprimir nem mesmo linhas completas em tempo real no Python 2 (for line in
iter (p.stdout.readline, '') `pode ser usado no lugar).for line in p.stdout:
funciona no Python 3. Certifique-se de entender a diferença entre''
(string Unicode) eb''
(bytes). Consulte Python: leia a entrada de streaming de subprocess.communicate ()Seu problema é:
for line in p.stdout: print(">>> " + str(line.rstrip())) p.stdout.flush()
o próprio iterador tem buffer extra.
Tente fazer assim:
while True: line = p.stdout.readline() if not line: break print line
fonte
Você não pode fazer com que o stdout imprima sem buffer em um pipe (a menos que você possa reescrever o programa que imprime no stdout), então aqui está minha solução:
Redirecione stdout para sterr, que não é armazenado em buffer.
'<cmd> 1>&2'
deve fazer isso. Abra o processo da seguinte maneira:myproc = subprocess.Popen('<cmd> 1>&2', stderr=subprocess.PIPE)
Você não pode distinguir de stdout ou stderr, mas obtém todas as saídas imediatamente.
Espero que isso ajude alguém a lidar com este problema.
fonte
1>&2
apenas muda para quais arquivos os descritores de arquivo apontam antes de iniciar o programa. O próprio programa não consegue distinguir entre redirecionar stdout para stderr (1>&2
) ou vice-versa (2>&1
), portanto, isso não terá efeito no comportamento de buffer do programa. E de qualquer forma, a1>&2
sintaxe é interpretada pelo shell.subprocess.Popen('<cmd> 1>&2', stderr=subprocess.PIPE)
iria falhar porque você não especificoushell=True
.Altere o stdout do processo rsync para sem buffer.
p = subprocess.Popen(cmd, shell=True, bufsize=0, # 0=unbuffered, 1=line-buffered, else buffer-size stdin=subprocess.PIPE, stderr=subprocess.PIPE, stdout=subprocess.PIPE)
fonte
Para evitar o cache de saída, você pode tentar o pexpect,
child = pexpect.spawn(launchcmd,args,timeout=None) while True: try: child.expect('\n') print(child.before) except pexpect.EOF: break
PS : Eu sei que essa questão é bem antiga, ainda fornecendo a solução que funcionou para mim.
PPS : obtive esta resposta de outra pergunta
fonte
p = subprocess.Popen(command, bufsize=0, universal_newlines=True)
Estou escrevendo uma GUI para rsync em python e tenho os mesmos problemas. Esse problema me incomodou por vários dias, até que eu encontrei isso no pyDoc.
Parece que o rsync irá imprimir '\ r' quando a tradução estiver acontecendo.
fonte
Percebi que não há menção ao uso de um arquivo temporário como intermediário. A seguir, contorna os problemas de buffer, gerando um arquivo temporário e permite que você analise os dados vindos do rsync sem conectar a um pty. Testei o seguinte em uma máquina Linux, e a saída do rsync tende a ser diferente entre as plataformas, portanto, as expressões regulares para analisar a saída podem variar:
import subprocess, time, tempfile, re pipe_output, file_name = tempfile.TemporaryFile() cmd = ["rsync", "-vaz", "-P", "/src/" ,"/dest"] p = subprocess.Popen(cmd, stdout=pipe_output, stderr=subprocess.STDOUT) while p.poll() is None: # p.poll() returns None while the program is still running # sleep for 1 second time.sleep(1) last_line = open(file_name).readlines() # it's possible that it hasn't output yet, so continue if len(last_line) == 0: continue last_line = last_line[-1] # Matching to "[bytes downloaded] number% [speed] number:number:number" match_it = re.match(".* ([0-9]*)%.* ([0-9]*:[0-9]*:[0-9]*).*", last_line) if not match_it: continue # in this case, the percentage is stored in match_it.group(1), # time in match_it.group(2). We could do something with it here...
fonte
while not p.poll()
leva a um loop infinito se o subprocesso sair com sucesso com 0, use emp.poll() is None
vez dissoopen(file_name)
pode falharcommand_argv = ["stdbuf","-i0","-o0","-e0"] + command_argv
e chamo:popen = subprocess.Popen(cmd, stdout=subprocess.PIPE)
e agora posso ler sem nenhum bufferse você executar algo assim em um thread e salvar a propriedade ffmpeg_time em uma propriedade de um método para que você possa acessá-lo, funcionaria muito bem, eu recebo saídas como esta: a saída será como se você usar threading no tkinter
input = 'path/input_file.mp4' output = 'path/input_file.mp4' command = "ffmpeg -y -v quiet -stats -i \"" + str(input) + "\" -metadata title=\"@alaa_sanatisharif\" -preset ultrafast -vcodec copy -r 50 -vsync 1 -async 1 \"" + output + "\"" process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, universal_newlines=True, shell=True) for line in self.process.stdout: reg = re.search('\d\d:\d\d:\d\d', line) ffmpeg_time = reg.group(0) if reg else '' print(ffmpeg_time)
fonte
No Python 3, aqui está uma solução, que tira um comando da linha de comando e entrega strings decodificadas em tempo real à medida que são recebidas.
Receptor (
receiver.py
):import subprocess import sys cmd = sys.argv[1:] p = subprocess.Popen(cmd, stdout=subprocess.PIPE) for line in p.stdout: print("received: {}".format(line.rstrip().decode("utf-8")))
Exemplo de programa simples que pode gerar saída em tempo real (
dummy_out.py
):import time import sys for i in range(5): print("hello {}".format(i)) sys.stdout.flush() time.sleep(1)
Resultado:
$python receiver.py python dummy_out.py received: hello 0 received: hello 1 received: hello 2 received: hello 3 received: hello 4
fonte