¿Cómo uso subprocess.Popen para conectar múltiples procesos por conductos?

8 minutos de lectura

¿Como uso subprocessPopen para conectar multiples procesos por conductos
Tomás

¿Cómo ejecuto el siguiente comando de shell usando Python? subprocess ¿módulo?

echo "input data" | awk -f script.awk | sort > outfile.txt

Los datos de entrada provendrán de una cadena, por lo que en realidad no necesito echo. He llegado hasta aquí, ¿alguien puede explicar cómo consigo que se canalice? sort ¿también?

p_awk = subprocess.Popen(["awk","-f","script.awk"],
                          stdin=subprocess.PIPE,
                          stdout=file("outfile.txt", "w"))
p_awk.communicate( "input data" )

ACTUALIZAR: Tenga en cuenta que si bien la respuesta aceptada a continuación no responde realmente a la pregunta formulada, creo que S. Lott tiene razón y es mejor evitar tener que resolver ese problema en primer lugar.

1646974874 71 ¿Como uso subprocessPopen para conectar multiples procesos por conductos
S. Lott

Estarías un poco más feliz con lo siguiente.

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" )

Delega parte del trabajo al shell. Deje que conecte dos procesos con una canalización.

Sería mucho más feliz reescribiendo ‘script.awk’ en Python, eliminando awk y la canalización.

Editar. Algunas de las razones para sugerir que awk no está ayudando.

[There are too many reasons to respond via comments.]

  1. Awk está agregando un paso sin valor significativo. No hay nada único en el procesamiento de awk que Python no maneje.

  2. La canalización de awk a sort, para grandes conjuntos de datos, puede mejorar el tiempo de procesamiento transcurrido. Para conjuntos cortos de datos, no tiene un beneficio significativo. Una medida rápida de awk >file ; sort file y awk | sort revelará ayudas de concurrencia. Con sort, rara vez ayuda porque sort no es un filtro de un solo paso.

  3. La simplicidad del procesamiento “Python to sort” (en lugar de “Python to awk to sort”) evita que se hagan exactamente el tipo de preguntas aquí.

  4. Python, aunque tiene más palabras que awk, también es explícito donde awk tiene ciertas reglas implícitas que son opacas para los novatos y confusas para los no especialistas.

  5. Awk (como el script de shell en sí) agrega otro lenguaje de programación. Si todo esto se puede hacer en un lenguaje (Python), eliminar el shell y la programación awk elimina dos lenguajes de programación, lo que permite que alguien se concentre en las partes de la tarea que generan valor.

En pocas palabras: awk no puede agregar un valor significativo. En este caso, awk es un costo neto; añadía tanta complejidad que era necesario hacer esta pregunta. Eliminar awk será una ganancia neta.

barra lateral Por qué construir una tubería (a | b) Es tan difícil.

Cuando el caparazón se enfrenta a a | b tiene que hacer lo siguiente.

  1. Fork un proceso secundario del shell original. Esto eventualmente se convertirá en b.

  2. Construya una tubería OS. (no es un subproceso de Python. PIPE) pero llama os.pipe() que devuelve dos nuevos descriptores de archivo que están conectados a través de un búfer común. En este punto, el proceso tiene stdin, stdout, stderr de su padre, más un archivo que será “a’s stdout” y “b’s stdin”.

  3. Tenedor de un niño. El hijo reemplaza su salida estándar con la salida estándar de la nueva a. ejecutivo el a proceso.

  4. El hijo b cierra reemplaza su stdin con el nuevo stdin de b. ejecutivo el b proceso.

  5. El hijo b espera a que a se complete.

  6. El padre está esperando que b se complete.

Creo que lo anterior se puede usar recursivamente para generar a | b | cpero tiene que poner implícitamente entre paréntesis las canalizaciones largas, tratándolas como si fueran a | (b | c).

Dado que Python tiene os.pipe(), os.exec() y os.fork()y puedes reemplazar sys.stdin y sys.stdout, hay una manera de hacer lo anterior en Python puro. De hecho, es posible que pueda resolver algunos atajos usando os.pipe() y subprocess.Popen.

Sin embargo, es más fácil delegar esa operación al shell.

  • Y creo que Awk es realmente una buena opción para lo que estoy haciendo, el código es más corto y simple que el código equivalente de Python (después de todo, es un lenguaje específico de dominio).

    – Tomás

    17 de noviembre de 2008 a las 23:40

  • -c le dice al shell (la aplicación real que está iniciando) que el siguiente argumento es un comando para ejecutar. En este caso, el comando es una canalización de shell.

    – S. Lott

    18 de noviembre de 2008 a las 0:26

  • “el código es más corto” no significa, en realidad, más simple. Sólo significa más corto. Awk tiene muchas suposiciones y características ocultas que hacen que sea muy difícil trabajar con el código. Python, aunque más largo, es explícito.

    – S. Lott

    18 de noviembre de 2008 a las 0:27

  • Claro, entiendo sus puntos y preocupaciones y estoy de acuerdo en que, en muchos casos, mi ejemplo anterior estaría mejor escrito en Python puro. Todavía no estoy listo para hacer eso en mi caso, ya que el script awk funciona y está depurado. Tarde o temprano, pero no ahora.

    – Tomás

    18 de noviembre de 2008 a las 0:57

  • Y eso no cambia la pregunta original, que es cómo usar subprocess.Popen. Awk y sort solo se usan con fines ilustrativos, ya que es probable que los posibles respondedores los tengan para probar.

    – Tomás

    18 de noviembre de 2008 a las 0:58

¿Como uso subprocessPopen para conectar multiples procesos por conductos
Cristian

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)

  • ¡excelente! Lo modifiqué para hacer un ejemplo autónomo sin el script awk, usa sed: sam.nipl.net/code/python/pipeline.py

    –Sam Watkins

    14 de marzo de 2013 a las 4:41


  • @SamWatkins: no necesitas p1.wait() en tu código. p1.communicate() cosecha el proceso hijo.

    – jfs

    11/09/2014 a las 18:19

  • ¿No es esta respuesta más pitónica y mejor? no usa shell=True como se desaconseja en la documentación del subproceso. No puedo ver la razón por la cual la gente votó a favor de la respuesta de @S.Lott.

    – Ken T.

    5 de junio de 2015 a las 4:22


  • @KenT: la solución de shell es más legible y menos propensa a errores (si no acepta entradas que no sean de confianza). La solución pitónica usaría plumbum (la sintaxis de shell incrustada en Python) u otro módulo que acepte una sintaxis similar (en una cadena) y construya la canalización para usted (el mismo comportamiento independientemente del entorno local). /bin/sh lo hace).

    – jfs

    28 de julio de 2015 a las 20:02

1646974876 924 ¿Como uso subprocessPopen para conectar multiples procesos por conductos
jfs

Para emular una canalización de shell:

from subprocess import check_call

check_call('echo "input data" | a | b > outfile.txt', shell=True)

sin invocar el shell (ver 17.1.4.2. Reemplazo de tubería de revestimiento):

#!/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 proporciona algo de azúcar de sintaxis:

#!/usr/bin/env python
from plumbum.cmd import a, b # magic

(a << "input data" | b > "outfile.txt")()

El análogo de:

#!/bin/sh
echo "input data" | awk -f script.awk | sort > outfile.txt

es:

#!/usr/bin/env python
from plumbum.cmd import awk, sort

(awk["-f", "script.awk"] << "input data" | sort > "outfile.txt")()

  • Plumbum se ve muy bien, pero desconfío de la “magia”. ¡Esto no es Perl!

    – Kyle Strand

    4 de diciembre de 2014 a las 17:52

  • ¡Plumbum se ve bien! No me preocuparía la magia @KyleStrand: con un vistazo rápido a los documentos, no es necesario que use los bits “mágicos”, el módulo tiene otras formas de hacer lo mismo, y una mirada rápida al código muestra que la magia es inofensiva y en realidad bastante ingeniosa, nada desagradable.

    – Tomás

    6 de diciembre de 2014 a las 9:13

  • @Tom No lo sé, eso es una gran sobrecarga de operadores con significados potencialmente sorprendentes. Una parte de mí lo ama, pero sería reacio a usarlo en cualquier lugar que no sea en un proyecto personal.

    – Kyle Strand

    8 de diciembre de 2014 a las 4:48

  • @KyleStrand: en general, estaría de acuerdo con usted, pero en la práctica es mucho más probable que las personas construyan la línea de comando de manera incorrecta (por ejemplo, olvidando pipes.quote()) o introducir errores al implementar la canalización en Python, incluso a | b podría implementarse con errores.

    – jfs

    12/03/2015 a las 22:22

  • @jfs, ¿cómo leo el archivo si el archivo llega a través de una solicitud POST usando el operador cat o <

    – CKM

    6 de noviembre de 2017 a las 11:53

1646974876 908 ¿Como uso subprocessPopen para conectar multiples procesos por conductos
Omry Yadan

La respuesta aceptada es eludir el problema. aquí hay un fragmento que encadena la salida de múltiples procesos: tenga en cuenta que también imprime el comando de shell (algo) equivalente para que pueda ejecutarlo y asegurarse de que la salida sea correcta.

#!/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)

1646974877 48 ¿Como uso subprocessPopen para conectar multiples procesos por conductos
geocar

http://www.python.org/doc/2.5.2/lib/node535.html cubrió esto bastante bien. ¿Hay alguna parte de esto que no entendiste?

Su programa sería bastante similar, pero el segundo Popen tendría stdout= a un archivo, y no necesitaría la salida de su .communicate().

  • Lo que no entiendo (dado el ejemplo de la documentación) es si digo p2.communicate(“datos de entrada”), ¿eso realmente se envía a p1.stdin?

    – Tomás

    17 de noviembre de 2008 a las 23:35

  • no lo harías El argumento estándar de p1 se establecería en PIPE y escribiría p1.communicate(‘foo’) y luego recogería los resultados haciendo p2.stdout.read()

    – geocar

    18 de noviembre de 2008 a las 21:30

  • @Leonid: la gente de Python no es muy buena en compatibilidad con versiones anteriores. Puede obtener gran parte de la misma información de: docs.python.org/2/library/subprocess.html#popen-objects pero de todos modos reemplacé el enlace con un enlace de máquina wayback.

    – geocar

    28 de junio de 2014 a las 12:48


  • No hay necesidad del sarcasmo de preguntar si hay “alguna parte de [the docs] que [OP] no entendí”. Como se muestra en esta pregunta, la parte de los documentos que publicó en realidad no aborda el problema de pasar la entrada al primer proceso: stackoverflow.com/q/6341451/1858225

    – Kyle Strand

    4 de diciembre de 2014 a las 17:42

1646974877 885 ¿Como uso subprocessPopen para conectar multiples procesos por conductos
I159

Inspirado en la respuesta de @Cristian. Me encontré con el mismo problema, pero con un comando diferente. Así que estoy poniendo mi ejemplo probado, que creo que podría 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()

Esto está probado.

Lo que se ha hecho

  • Declarado perezoso grep ejecución con stdin desde pipe. Este comando se ejecutará en el ps ejecución del comando cuando la tubería se llenará con la salida estándar de ps.
  • Llamado el comando principal ps con stdout dirigido a la tubería utilizada por el grep mando.
  • Grep se comunicó para obtener la salida estándar de la tubería.

Me gusta de esta manera porque es una concepción de tubería natural envuelta suavemente con subprocess interfaces

  • Lo que no entiendo (dado el ejemplo de la documentación) es si digo p2.communicate(“datos de entrada”), ¿eso realmente se envía a p1.stdin?

    – Tomás

    17 de noviembre de 2008 a las 23:35

  • no lo harías El argumento estándar de p1 se establecería en PIPE y escribiría p1.communicate(‘foo’) y luego recogería los resultados haciendo p2.stdout.read()

    – geocar

    18 de noviembre de 2008 a las 21:30

  • @Leonid: la gente de Python no es muy buena en compatibilidad con versiones anteriores. Puede obtener gran parte de la misma información de: docs.python.org/2/library/subprocess.html#popen-objects pero de todos modos reemplacé el enlace con un enlace de máquina wayback.

    – geocar

    28 de junio de 2014 a las 12:48


  • No hay necesidad del sarcasmo de preguntar si hay “alguna parte de [the docs] que [OP] no entendí”. Como se muestra en esta pregunta, la parte de los documentos que publicó en realidad no aborda el problema de pasar la entrada al primer proceso: stackoverflow.com/q/6341451/1858225

    – Kyle Strand

    4 de diciembre de 2014 a las 17:42

1646974878 368 ¿Como uso subprocessPopen para conectar multiples procesos por conductos
tío

Las respuestas anteriores perdieron un punto importante. Reemplazo de tubería de revestimiento es básicamente correcto, como lo señala geocar. Está casi suficiente para correr communicate en el último elemento de la tubería.

El problema restante es pasar el datos de entrada a la tubería. Con múltiples subprocesos, un simple communicate(input_data) en el último elemento no funciona, se cuelga para siempre. Debe crear una canalización y un niño manualmente como este:

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())

Ahora el hijo proporciona la entrada a través de la canalización y el padre llama a communicar(), que funciona como se esperaba. Con este enfoque, puede crear canalizaciones largas arbitrarias sin recurrir a “delegar parte del trabajo al shell”. Desafortunadamente, el documentación de subprocesos no menciona esto.

Hay formas de lograr el mismo efecto sin tuberías:

from tempfile import TemporaryFile
tf = TemporaryFile()
tf.write(input)
tf.seek(0, 0)

Ahora usa stdin=tf por p_awk. Es cuestión de gustos lo que prefieras.

Lo anterior todavía no es 100% equivalente a las canalizaciones bash porque el manejo de la señal es diferente. Puede ver esto si agrega otro elemento de tubería que trunca la salida de sortp.ej head -n 10. Con el código de arriba, sort imprimirá un mensaje de error “Tubo roto” para stderr. No verá este mensaje cuando ejecute la misma canalización en el shell. (Sin embargo, esa es la única diferencia, el resultado en stdout es el mismo). La razón parece ser que Python Popen conjuntos SIG_IGN por SIGPIPEmientras que la cáscara lo deja en SIG_DFLy sortEl manejo de la señal de es diferente en estos dos casos.

  • es suficiente para ejecutar comunicarse en el último proceso, vea mi respuesta.

    – Omry Yadan

    9 de diciembre de 2018 a las 1:57

¿Ha sido útil esta solución?

Esta web utiliza cookies propias y de terceros para su correcto funcionamiento y para fines analíticos y para mostrarte publicidad relacionada con sus preferencias en base a un perfil elaborado a partir de tus hábitos de navegación. Al hacer clic en el botón Aceptar, acepta el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Configurar y más información
Privacidad