transmisión de datos en el comando con subprocess.Popen

Con frecuencia necesito ordenar una colección de archivos que contienen encabezados. Debido a que la clasificación depende del contenido del encabezado, este caso de uso es más complicado que las preguntas similares (por ejemplo, ¿Hay alguna forma de ignorar las líneas de encabezado en una clasificación UNIX? ).

Tenía la esperanza de usar Python para leer archivos, generar el encabezado del primer archivo, y luego ordenar las colas en orden. He intentado esto como una prueba de concepto:

#!/usr/bin/env python import io import subprocess import sys header_printed = False sorter = subprocess.Popen(['sort'], stdin=subprocess.PIPE) for f in sys.argv[1:]: fd = io.open(f,'r') line = fd.readline() if not header_printed: print(line) header_printed = True sorter.communicate(line) 

Cuando se llama como header-sort fileA fileB , con archivoA y archivoB que contienen líneas como

 c float int Y 0.557946 413 F 0.501935 852 F 0.768102 709 

Yo obtengo:

 # sort file 1 Traceback (most recent call last): File "./archive/bin/pipetest", line 17, in  sorter.communicate(line) File "/usr/lib/python2.7/subprocess.py", line 785, in communicate self.stdin.write(input) ValueError: I/O operation on closed file 

El problema es comunicar toma una cadena y la tubería se cierra después de escribir. Esto significa que el contenido debe leerse completamente en la memoria. Comunicar no toma un generador (lo intenté).

Una demostración aún más simple de esto es:

 >>> import subprocess >>> p = subprocess.Popen(['tr', 'a-z', 'A-Z'], stdin=subprocess.PIPE) >>> p.communicate('hello') HELLO(None, None) >>> p.communicate('world') Traceback (most recent call last): File "", line 1, in  p.communicate('world') File "/usr/lib/python2.7/subprocess.py", line 785, in communicate self.stdin.write(input) ValueError: I/O operation on closed file 

Entonces, la pregunta es, ¿cuál es la forma correcta (con Popen o de otra manera) de transmitir datos a una tubería en Python?

Solo escribe directamente a la tubería:

 #!/usr/bin/env python2 import fileinput import subprocess process = subprocess.Popen(['sort'], stdin=subprocess.PIPE) with process.stdin as pipe, fileinput.FileInput() as file: for line in file: if file.isfirstline(): # print header print line, else: # pipe tails pipe.write(line) process.wait() 

Para su caso específico, si solo pasó el subprocess.PIPE para un solo identificador estándar (en su caso, stdin ), en su ejemplo, puede llamar a sorter.stdin.write(line) una y otra vez. Cuando haya terminado de escribir la salida, llame a sorter.stdin.close() para que la sort sepa que la entrada está terminada, y puede realizar el trabajo de ordenación y salida real ( sorter.communicate() sin ningún argumento probablemente también funcionaría; de lo contrario, después de cerrar stdin probablemente querrá llamar sorter.wait() para dejar que termine).

Si necesita lidiar con más de un mango estándar entubado, la manera correcta es threading con una rosca dedicada para cada tubería que debe manejarse más allá de la primera (concepto relativamente simple, pero pesado e introduce todos los dolores de cabeza de la rosca). o usar el módulo de select (o en Python 3.4+, el módulo de selectors ), que es bastante difícil de hacer bien, pero puede (en algunas circunstancias) ser más eficiente. Por último, se crean archivos temporales para la salida , por lo que puede escribir directamente en la stdin del proceso mientras el proceso se escribe en un archivo (y, por lo tanto, no se bloqueará); luego puede leer el archivo a su gusto (tenga en cuenta que el subproceso no necesariamente habrá vaciado sus propios buffers de salida hasta que salga, por lo que es posible que la salida no llegue rápidamente en respuesta a su entrada hasta que más entradas y salidas se hayan llenado y vaciado buffer).

El método .communicate() subprocess.Popen utiliza subprocesos o select primitivas de módulo (según el soporte del sistema operativo; la implementación está bajo los diversos métodos _communicate aquí ) siempre que pase subprocess.PIPE para más de uno de los identificadores estándar; Es como tienes que hacerlo.

Puede utilizar la escritura / lectura desde stdin y stdout , sin embargo, dependiendo de su subproceso, necesita un “mecanismo de descarga” para que el subproceso procese su entrada. El siguiente código funciona para la primera parte, pero como cierra stdin , también mata el subproceso. Si lo cambia con flush() o si puede agregar algunos caracteres finales para impulsar su subproceso, entonces puede usarlo. De lo contrario, recomendaría echar un vistazo a Multithreading en Python , especialmente sobre pipes .

 p=subprocess.Popen(['tr','a-z','A-Z'],stdin=subprocess.PIPE,stdout=subprocess.PIPE) p.stdin.write("hello\n") p.stdin.close() p.stdout.readline() 'HELLO\n'