¿Cómo uso subprocess.Popen para conectar múltiples procesos mediante tuberías?

Resuelto Tom asked hace 15 años • 9 respuestas

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

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 los necesito echo. He llegado hasta aquí, ¿alguien puede explicar cómo consigo que pase sorttambién?

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

ACTUALIZACIÓN : Tenga en cuenta que, si bien la respuesta aceptada a continuación en realidad no responde la pregunta tal como se hizo, creo que S.Lott tiene razón y es mejor evitar tener que resolver ese problema en primer lugar.

Tom avatar Nov 17 '08 19:11 Tom
Aceptado

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

Delegar parte del trabajo al shell. Deje que conecte dos procesos con una tubería.

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 ayuda.

[Hay demasiadas razones para responder mediante comentarios].

  1. Awk está agregando un paso sin valor significativo. No hay nada exclusivo en el procesamiento de awk que Python no pueda manejar.

  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 ningún beneficio significativo. Es útil realizar una medición rápida awk >file ; sort filey una awk | sortrevelación de la simultaneidad. Con la clasificación, rara vez ayuda porque la clasificación no es un filtro de una sola vez.

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

  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 propio script de shell) agrega otro lenguaje de programación más. Si todo esto se puede hacer en un lenguaje (Python), al eliminar el shell y la programación awk se eliminan dos lenguajes de programación, lo que permite que alguien se concentre en las partes de la tarea que producen valor.

En pocas palabras: awk no puede agregar un valor significativo. En este caso, awk es un costo neto; añadió suficiente complejidad como para que fuera necesario hacer esta pregunta. Eliminar awk será una ganancia neta.

Barra laterala | b Por qué es tan difícil construir un oleoducto ( ).

Cuando se enfrenta al caparazón, a | btiene que hacer lo siguiente.

  1. Bifurca un proceso hijo del shell original. Esto eventualmente se convertirá en b.

  2. Construya una tubería de sistema operativo. (no es un subproceso de Python.PIPE), pero una llamada os.pipe()que devuelve dos nuevos descriptores de archivos 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, además de un archivo que será "a's stdout" y "b's stdin".

  3. Bifurca a un niño. El niño reemplaza su salida estándar con la salida estándar de la nueva a. Ejecutar el aproceso.

  4. El cierre del niño b reemplaza su entrada estándar con la entrada estándar de la nueva b. Ejecutar el bproceso.

  5. El niño b espera a que se complete a.

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

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

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

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

S.Lott avatar Nov 17 '2008 13:11 S.Lott
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)
Cristian avatar Feb 06 '2012 17:02 Cristian

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 la canalización del shell ):

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

plumbumproporciona 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")()
jfs avatar May 23 '2013 09:05 jfs

La respuesta aceptada es eludir la pregunta real. 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)
Omry Yadan avatar Dec 09 '2018 00:12 Omry Yadan