2010-10-05 9 views
9

Me postulo la siguiente versión de Python:¿Cómo cierro un subproceso de Python 2.5.2 Popen una vez que tengo los datos que necesito?

$ /usr/bin/env python --version                                        
Python 2.5.2          

estoy corriendo el siguiente código Python para escribir datos desde un subproceso hijo a la salida estándar, y la lectura que en una variable de Python llamada metadata:

# Extract metadata (snippet from extractMetadata.py) 
inFileAsGzip = "%s.gz" % inFile                                                    
if os.path.exists(inFileAsGzip):                                                   
    os.remove(inFileAsGzip)                                                     
os.symlink(inFile, inFileAsGzip)                                                   
extractMetadataCommand = "bgzip -c -d -b 0 -s %s %s" % (metadataRequiredFileSize, inFileAsGzip)                                    
metadataPipes = subprocess.Popen(extractMetadataCommand, stdin=None, stdout=subprocess.PIPE, shell=True, close_fds=True)                          
metadata = metadataPipes.communicate()[0]                                                                                                   
metadataPipes.stdout.close()                                                    
os.remove(inFileAsGzip) 
print metadata 

el caso de uso es el siguiente, para tirar de los primeros diez líneas de salida estándar a partir del código antes mencionado fragmento:

$ extractMetadata.py | head 

aparecerá el error si la tubería I en la cabeza, awk, grep, etc.

El guión termina con el siguiente error:

close failed: [Errno 32] Broken pipe 

yo habría pensado que el cierre de las tuberías sería suficiente, pero, obviamente, ese no es el caso.

+1

Este código funciona bien para mí después de ponerlo a utilizar gzip. Si solo está descomprimiendo archivos gzip, ¿por qué está utilizando una herramienta oscura que nadie conoce ("bgzip")? Google encuentra solo un proyecto de años difuntos en SF. Use zcat o, mejor aún, use el módulo gzip. No es probable que obtenga una respuesta a esto si no proporciona una muestra de código que reproduzca el problema. –

+0

He editado la pregunta para mostrar cuál es el caso de uso que desencadena la tubería rota. El código que he proporcionado debería ser suficiente, creo. Si reemplazo "bgzip" por "gzip" obtengo el mismo error. La herramienta "bgzip" es parte de un conjunto denominado SAMtools, que incluye una versión modificada de "gzip" que realiza un acceso aleatorio dentro del archivo. Espero que esto ayude. –

Respuesta

4

Hmmm. He visto alguna extrañeza de "tubería rota" con subproceso + gzip. Nunca entendí exactamente por qué estaba sucediendo, pero al cambiar mi enfoque de implementación, pude evitar el problema. Parece que estás tratando de usar un proceso back-end gzip para descomprimir un archivo (probablemente porque el módulo integrado de Python es tremendamente lento ... no sé por qué pero definitivamente lo es).

En lugar de usar communicate() puede, en cambio, tratar el proceso como un backend totalmente asincrónico y simplemente leer su salida cuando llega. Cuando el proceso muere, el módulo de subproceso se encargará de limpiarlo todo. El siguiente snippit debería proporcionar la misma funcionalidad básica sin ningún problema de tubería rota.

import subprocess 

gz_proc = subprocess.Popen(['gzip', '-c', '-d', 'test.gz'], stdout=subprocess.PIPE) 

l = list() 
while True: 
    dat = gz_proc.stdout.read(4096) 
    if not d: 
     break 
    l.append(d) 

file_data = ''.join(l) 
+0

Gracias por su respuesta. Todavía tengo errores de tubería rotos con este enfoque. Quizás Popen() y write() no cooperan bien con respecto a la salida de tubería a un shell csh/bash. –

0

No hay suficiente información para responder a esto de manera concluyente, pero puedo hacer algunas conjeturas.

En primer lugar, os.remove no debería estar fallando con EPIPE. No se ve como es, tampoco; el error es close failed: [Errno 32] Broken pipe, no remove failed. Parece que close está fallando, no remove.

Es posible que el error de cierre de una tubería termine. Si los datos están almacenados en un búfer, Python borrará los datos antes de cerrar el archivo. Si el proceso subyacente se ha ido, al hacer esto aumentará IOError/EPIPE. Sin embargo, tenga en cuenta que esto no es un error fatal: incluso cuando esto sucede, el archivo aún está cerrado. El siguiente código reproduce esto aproximadamente el 50% del tiempo y demuestra que el archivo se cierra después de la excepción. (Cuidado, creo que el comportamiento de bufsize ha cambiado en todas las versiones).

import os, subprocess 
    metadataPipes = subprocess.Popen("echo test", stdin=subprocess.PIPE, 
     stdout=subprocess.PIPE, shell=True, close_fds=True, bufsize=4096) 
    metadataPipes.stdin.write("blah"*1000) 
    print metadataPipes.stdin 
    try: 
     metadataPipes.stdin.close() 
    except IOError, e: 
     print "stdin after failure: %s" % metadataPipes.stdin 

This is racy; solo pasa una parte del tiempo. Eso puede explicar por qué parecía eliminar o agregar la llamada os.remove que afecta el error.

Dicho esto, no veo cómo esto podría pasar con el código que ha proporcionado, ya que no escribe en stdin. Sin embargo, es lo más cercano que puedo obtener sin una reproducción utilizable, y tal vez te indique la dirección correcta.

Como nota al margen, no debe comprobar os.path.existe antes de eliminar un archivo que puede no existir; causará condiciones de carrera si otro proceso elimina el archivo al mismo tiempo. En su lugar, hacer esto:

try: 
    os.remove(inFileAsGzip) 
except OSError, e: 
    if e.errno != errno.ENOENT: raise 

... que por lo general debe ser envuelta en una función como rm_f.

Finalmente, si desea eliminar un subproceso explícitamente, está metadataPipes.kill, simplemente cerrar las tuberías no hará eso, pero eso no ayuda a explicar el error. Además, nuevamente, si solo está leyendo archivos gzip, está mucho mejor con el módulo gzip que con un subproceso. http://docs.python.org/library/gzip.html

+0

kill() no está disponible en 2.5.2. No hay stdin; He editado la pregunta para reflejar esto. No puedo usar el módulo gzip para esta tarea, aunque el uso del binario gzip reproduce los errores. –

+0

La pregunta no tiene sentido ahora: está ejecutando el script a través de 'head', pero el script no tiene salida. Proporcione una reproducción completa y autónoma; no nos haga experimentar con código parcial que ni siquiera se ejecuta tal como está, tratando de adivinar de qué está hablando. –

+0

De acuerdo, no importa. Gracias por tu ayuda. –

0

conseguir las primeras 10 líneas de una salida de proceso podría funcionar mejor de esta manera:

ph = os.popen(cmdline, 'r') 
lines = [] 
for s in ph: 
    lines.append(s.rstrip()) 
    if len(lines) == 10: break 
print '\n'.join(lines) 
ph.close() 
+0

¿Qué sucede si quiero manejar la salida estándar de manera diferente? En otras palabras, en lugar de usar head, canalizo la salida en un script awk. Tengo una tubería rota cada vez que canalizo la salida a otra parte. –

+0

Si mi versión anterior de la secuencia de comandos falla cuando se procesa a través de awk, entonces su problema no tiene nada que ver con el subproceso. ¿Qué me estoy perdiendo? – Vlad

1

creo que esta excepción no tiene nada que ver con el subproceso llamada ni sus descriptores de archivos (después de llamar comunican el objeto popen está cerrado). Este parece ser el clásico problema del cierre sys.stdout en una tubería:

http://bugs.python.org/issue1596

A pesar de ser un error de 3 años de edad no ha sido resuelto. Desde sys.stdout.write(...) no parece ayudar a cualquiera, es posible recurrir a una llamada de nivel inferior, pruebe esto:

os.write(sys.stdout.fileno(), metadata) 
Cuestiones relacionadas