Como eu uso subprocess.Popen para conectar vários processos por tubos?
-
08-07-2019 - |
Pergunta
Como faço para executar o seguinte comando shell usando o Python subprocess
módulo?
echo "input data" | awk -f script.awk | sort > outfile.txt
Os dados de entrada virá de uma corda, então eu realmente não precisa echo
. Eu tenho até aqui, alguém pode explicar como eu obtê-lo a tubulação através sort
também?
p_awk = subprocess.Popen(["awk","-f","script.awk"],
stdin=subprocess.PIPE,
stdout=file("outfile.txt", "w"))
p_awk.communicate( "input data" )
Atualizar : Nota que, enquanto a resposta aceite abaixo na verdade não responder à pergunta como pediu, eu acredito S.Lott é certo eo que é melhor para evitar ter de resolver esse problema em primeiro lugar !
Solução
Você seria um pouco mais feliz com o seguinte.
import subprocess
awk_sort = subprocess.Popen( "awk -f script.awk | sort > outfile.txt",
stdin=subprocess.PIPE, shell=True )
awk_sort.communicate( b"input data\n" )
delegar parte do trabalho para o shell. Deixá-lo conectar dois processos com um pipeline.
Você ficaria muito mais feliz reescrita 'script.awk' em Python, eliminando awk e do gasoduto.
Editar . Algumas das razões para sugerir que awk não está ajudando.
[Há muitas razões para responder através de comentários.]
-
Awk está adicionando um passo de nenhum valor significativo. Não há nada especial sobre o processamento do awk que Python não controla.
- tempo de processamento
O pipeline de awk para classificar, para grandes conjuntos de dados, pode melhorar decorrido. Para conjuntos curtos de dados, ele não tem nenhum benefício significativo. Uma medição rápida de
awk >file ; sort file
eawk | sort
irá revelar de simultaneidade ajuda. Com sorte, ele raramente ajuda porque espécie não é um filtro, uma vez-through. -
A simplicidade de "Python para ordenar" o processamento (em vez de "Python para awk à sorte") impede que o exato tipo de perguntas feitas aqui.
-
Python - enquanto verborrágico de awk -. Também é explícita, onde awk tem certas regras implícitas que são opacos para iniciantes, e confuso para os não-especialistas
-
Awk (como o shell script em si), adiciona uma nova linguagem de programação. Se tudo isso pode ser feito em uma linguagem (Python), eliminando o reservatório eo awk programação elimina duas linguagens de programação, permitindo que alguém para se concentrar nas partes produtoras de valor da tarefa.
A linha inferior: awk não pode adicionar um valor significativo. Neste caso, awk é um custo líquido; acrescentou o suficiente complexidade que era necessário para fazer esta pergunta. Removendo awk será um ganho líquido.
Sidebar Por que a construção de um pipeline (a | b
) é tão difícil.
Quando o shell é confrontado com a | b
ele tem que fazer o seguinte.
-
Fork um processo filho do shell originais. Isto acabará por se tornar b.
-
Construir uma tubulação de os. (Não um subprocess.PIPE Python), mas
os.pipe()
chamada que retorna dois novos descritores de arquivos que estão conectados via tampão comum. Neste ponto, o processo tem stdin, stdout, stderr de seu pai, além de um arquivo que será "stdout de um" e "stdin do b". -
Fork uma criança. A criança substitui seu stdout com stdout do novo um. Exec o processo
a
. -
A criança fecha b substitui seu stdin com stdin do novo b. Exec o processo
b
. -
Os b criança aguarda uma para completar.
-
O pai está esperando por b para ser concluído.
Eu acho que o acima pode ser usado de forma recursiva para a | b | c
desova, mas você tem que entre parênteses implicitamente pipelines longos, tratando-os como se eles estão a | (b | c)
.
Desde Python tem os.pipe()
, os.exec()
e os.fork()
, e você pode substituir sys.stdin
e sys.stdout
, há uma maneira de fazer o acima em puro Python. Na verdade, você pode ser capaz de trabalhar fora alguns atalhos usando os.pipe()
e subprocess.Popen
.
No entanto, é mais fácil delegar essa operação para o shell.
Outras dicas
import subprocess
some_string = b'input_data'
sort_out = open('outfile.txt', 'wb', 0)
sort_in = subprocess.Popen('sort', stdin=subprocess.PIPE, stdout=sort_out).stdin
subprocess.Popen(['awk', '-f', 'script.awk'], stdout=sort_in,
stdin=subprocess.PIPE).communicate(some_string)
Para emular um gasoduto shell:
from subprocess import check_call
check_call('echo "input data" | a | b > outfile.txt', shell=True)
sem invocar o shell (ver 17.1.4.2. Substituir shell gasoduto):
#!/usr/bin/env python
from subprocess import Popen, PIPE
a = Popen(["a"], stdin=PIPE, stdout=PIPE)
with a.stdin:
with a.stdout, open("outfile.txt", "wb") as outfile:
b = Popen(["b"], stdin=a.stdout, stdout=outfile)
a.stdin.write(b"input data")
statuses = [a.wait(), b.wait()] # both a.stdin/stdout are closed already
plumbum
fornece um pouco de açúcar sintaxe:
#!/usr/bin/env python
from plumbum.cmd import a, b # magic
(a << "input data" | b > "outfile.txt")()
O análogo de:
#!/bin/sh
echo "input data" | awk -f script.awk | sort > outfile.txt
é:
#!/usr/bin/env python
from plumbum.cmd import awk, sort
(awk["-f", "script.awk"] << "input data" | sort > "outfile.txt")()
http://www.python.org/doc/2.5.2/lib/node535.html cobriu isso muito bem. Existe alguma parte deste você não entendeu?
Seu programa seria muito semelhante, mas o segundo Popen
teria stdout = para um arquivo, e você não precisa a saída de seu .communicate()
.
Inspirado pela resposta da @ Cristian. Conheci apenas o mesmo problema, mas com um comando diferente. Então, eu estou colocando meu exemplo testado, o que eu acredito que poderia ser útil:
grep_proc = subprocess.Popen(["grep", "rabbitmq"],
stdin=subprocess.PIPE,
stdout=subprocess.PIPE)
subprocess.Popen(["ps", "aux"], stdout=grep_proc.stdin)
out, err = grep_proc.communicate()
Esta é testada.
O que foi feito
- Declarado execução
grep
preguiçoso com stdin da tubulação. Este comando será executado na execução do comandops
quando o tubo será preenchido com o stdout deps
. - Chamado de
ps
comando primário com stdout direcionado para o tubo utilizado pelo comandogrep
. - Grep comunicado para obter stdout do tubo.
Eu gosto desta maneira porque é a concepção tubo naturais delicadamente enrolado com interfaces subprocess
.
A resposta aceita é contornar a questão. Aqui está um trecho que as cadeias a saída de vários processos: Note que também imprime o (um pouco) o comando equivalente shell para que você possa executá-lo e certifique-se a saída está correta.
#!/usr/bin/env python3
from subprocess import Popen, PIPE
# cmd1 : dd if=/dev/zero bs=1m count=100
# cmd2 : gzip
# cmd3 : wc -c
cmd1 = ['dd', 'if=/dev/zero', 'bs=1M', 'count=100']
cmd2 = ['tee']
cmd3 = ['wc', '-c']
print(f"Shell style : {' '.join(cmd1)} | {' '.join(cmd2)} | {' '.join(cmd3)}")
p1 = Popen(cmd1, stdout=PIPE, stderr=PIPE) # stderr=PIPE optional, dd is chatty
p2 = Popen(cmd2, stdin=p1.stdout, stdout=PIPE)
p3 = Popen(cmd3, stdin=p2.stdout, stdout=PIPE)
print("Output from last process : " + (p3.communicate()[0]).decode())
# thoretically p1 and p2 may still be running, this ensures we are collecting their return codes
p1.wait()
p2.wait()
print("p1 return: ", p1.returncode)
print("p2 return: ", p2.returncode)
print("p3 return: ", p3.returncode)
EDIT: pipes
está disponível no Windows, mas, crucialmente, não parece realmente trabalho no Windows. Ver comentários abaixo.
O Python biblioteca padrão agora inclui o módulo pipes
para manusear este:
https://docs.python.org/2/library/pipes.html , https://docs.python.org/3.4/library/pipes.html
Eu não sei quanto tempo este módulo tem sido em torno, mas esta abordagem parece ser muito mais simples do que mucking com subprocess
.
As respostas anteriores perdeu um ponto importante. Substituindo gasoduto shell é basicamente correta, como fora pontas por geocar. É quase suficiente para executar communicate
no último elemento do tubo.
O problema restante é passar os dados de entrada para o pipeline. Com vários subprocessos, um simples communicate(input_data)
no último elemento não funciona - ele trava para sempre. Você precisa criar um um gasoduto e uma criança manualmente assim:
import os
import subprocess
input = """\
input data
more input
""" * 10
rd, wr = os.pipe()
if os.fork() != 0: # parent
os.close(wr)
else: # child
os.close(rd)
os.write(wr, input)
os.close(wr)
exit()
p_awk = subprocess.Popen(["awk", "{ print $2; }"],
stdin=rd,
stdout=subprocess.PIPE)
p_sort = subprocess.Popen(["sort"],
stdin=p_awk.stdout,
stdout=subprocess.PIPE)
p_awk.stdout.close()
out, err = p_sort.communicate()
print (out.rstrip())
Agora, a criança fornece a entrada através do tubo, e as chamadas de pais comunicar (), que funciona como o esperado. Com esta abordagem, você pode criar pipelines longos arbitrárias, sem recorrer a "parte delegar parte do trabalho para o shell". Infelizmente, a documentação subprocess não menciona isso.
Existem maneiras de conseguir o mesmo efeito sem tubos:
from tempfile import TemporaryFile
tf = TemporaryFile()
tf.write(input)
tf.seek(0, 0)
Agora uso stdin=tf
para p_awk
. É uma questão de gosto que você preferir.
O acima ainda não é 100% em equivalentes de condutas Bash porque o manuseamento de sinal é diferente. Você pode ver isso se você adicionar outro elemento tubo que trunca a saída do sort
, por exemplo, head -n 10
. Com o código acima, sort
irá imprimir uma mensagem de erro "cano quebrado" para stderr
. Você não verá essa mensagem quando você executa o mesmo oleoduto no shell. (Essa é a única diferença, porém, o resultado em stdout
é o mesmo). A razão parece ser sets Popen
que de python SIG_IGN
para SIGPIPE
, enquanto que as folhas de casca de TI em SIG_DFL
e manipulação de sinais de sort
é diferente nestes dois casos.
Para mim, a seguir abordagem é a mais limpa e mais fácil de ler
from subprocess import Popen, PIPE
def string_to_2_procs_to_file(input_s, first_cmd, second_cmd, output_filename):
with open(output_filename, 'wb') as out_f:
p2 = Popen(second_cmd, stdin=PIPE, stdout=out_f)
p1 = Popen(first_cmd, stdout=p2.stdin, stdin=PIPE)
p1.communicate(input=bytes(input_s))
p1.wait()
p2.stdin.close()
p2.wait()
que pode ser chamado assim:
string_to_2_procs_to_file('input data', ['awk', '-f', 'script.awk'], ['sort'], 'output.txt')