2008-10-02 7 views
221

Si hago lo siguiente:Python - ¿Cómo paso una cadena al subproceso.Popen (usando el argumento stdin)?

import subprocess 
from cStringIO import StringIO 
subprocess.Popen(['grep','f'],stdout=subprocess.PIPE,stdin=StringIO('one\ntwo\nthree\nfour\nfive\nsix\n')).communicate()[0] 

me sale:

Traceback (most recent call last): 
    File "<stdin>", line 1, in ? 
    File "/build/toolchain/mac32/python-2.4.3/lib/python2.4/subprocess.py", line 533, in __init__ 
    (p2cread, p2cwrite, 
    File "/build/toolchain/mac32/python-2.4.3/lib/python2.4/subprocess.py", line 830, in _get_handles 
    p2cread = stdin.fileno() 
AttributeError: 'cStringIO.StringI' object has no attribute 'fileno' 

parecer un objeto cStringIO.StringIO no Quack lo suficientemente cerca de un pato archivo para que se adapte subprocess.Popen. ¿Cómo trabajo alrededor de esto?

+3

En lugar de disputar mi respuesta con esta siendo eliminada, estoy añadiendo como un comentario ... Lectura recomendada: [Python de Doug Hellmann Módulo de la publicación de blog de la semana en el subproceso] (http://www.doughellmann.com/PyMOTW/subprocess/). –

+3

la publicación del blog contiene múltiples errores, por ejemplo, [el primer ejemplo de código: 'call (['ls', '-1'], shell = True)'] (http://www.doughellmann.com/PyMOTW/subprocess /) Es incorrecto. Recomiendo leer [preguntas comunes de la descripción de la etiqueta del subproceso] (http://stackoverflow.com/tags/subprocess/info) en su lugar. En particular, [¿Por qué el subproceso.Popen no funciona cuando args es la secuencia?] (Http://stackoverflow.com/q/2400878/4279) explica por qué 'call (['ls', '-1'], shell = Verdadero) 'es incorrecto. Recuerdo haber dejado comentarios en la publicación del blog, pero ahora no los veo por algún motivo. – jfs

Respuesta

248

Popen.communicate() documentación:

Tenga en cuenta que si desea enviar datos a entrada estándar del proceso, es necesario crear el objeto Popen con entrada estándar = PIPE. De forma similar, para obtener que no sea None en la tupla de resultado, , debe indicar stdout = PIPE y/o stderr = PIPE también.

Sustitución os.popen *

pipe = os.popen(cmd, 'w', bufsize) 
    # ==> 
    pipe = Popen(cmd, shell=True, bufsize=bufsize, stdin=PIPE).stdin 

Advertencia Uso comunicarse() en lugar de stdin.write(), stdout.read() o stderr.read() para evitar interbloqueos debido a a cualquiera de las otras memorias intermedias de canal del sistema operativo llenando y bloqueando el proceso secundario.

Así que su ejemplo se podría escribir de la siguiente manera:

from subprocess import Popen, PIPE, STDOUT 

p = Popen(['grep', 'f'], stdout=PIPE, stdin=PIPE, stderr=STDOUT)  
grep_stdout = p.communicate(input=b'one\ntwo\nthree\nfour\nfive\nsix\n')[0] 
print(grep_stdout.decode()) 
# -> four 
# -> five 
# -> 

En la versión actual de Python 3, se puede usar subprocess.run, para pasar de entrada como una cadena a un comando externo y obtener su estado de salida, y su salida como una cadena de nuevo en una llamada:

#!/usr/bin/env python3 
from subprocess import run, PIPE 

p = run(['grep', 'f'], stdout=PIPE, 
     input='one\ntwo\nthree\nfour\nfive\nsix\n', encoding='ascii') 
print(p.returncode) 
# -> 0 
print(p.stdout) 
# -> four 
# -> five 
# -> 
+3

Me perdí esa advertencia. Me alegro de haber preguntado (aunque pensé que tenía la respuesta). –

+8

Esta NO es una buena solución. En particular, no puede procesar asincrónicamente la salida p.stdout.readline si hace esto, ya que tendría que esperar a que llegue la stdout completa. También es ineficiente en la memoria. – OTZ

+5

@OTZ ¿Cuál es la mejor solución? –

34

me di cuenta de esta solución:

>>> p = subprocess.Popen(['grep','f'],stdout=subprocess.PIPE,stdin=subprocess.PIPE) 
>>> p.stdin.write(b'one\ntwo\nthree\nfour\nfive\nsix\n') #expects a bytes type object 
>>> p.communicate()[0] 
'four\nfive\n' 
>>> p.stdin.close() 

¿Hay una mejor?

+6

No es una solución alternativa: ¡esa es la forma correcta de hacerlo! – Moe

+17

@Moe: se desaconseja el uso de 'stdin.write()', se debe usar 'p.communicate()'. Ver mi respuesta – jfs

+8

Según la documentación del subproceso: Advertencia: utilice comunicar() en lugar de .stdin.write, .stdout.read o .stderr.read para evitar interbloqueos debido a que cualquiera de los demás búferes de canal del SO se llenan y bloquean el proceso secundario. –

12

"Al parecer un objeto cStringIO.StringIO no charlatán lo suficientemente cerca de un pato archivo para que se adapte subprocess.Popen"

:-)

no tengo miedo. El conducto es un concepto de sistema operativo de bajo nivel, por lo que requiere absolutamente un objeto de archivo que esté representado por un descriptor de archivo de nivel de sistema operativo. Su solución alternativa es la correcta.

2
p = Popen(['grep', 'f'], stdout=PIPE, stdin=PIPE, stderr=STDOUT)  
p.stdin.write('one\n') 
time.sleep(0.5) 
p.stdin.write('two\n') 
time.sleep(0.5) 
p.stdin.write('three\n') 
time.sleep(0.5) 
testresult = p.communicate()[0] 
time.sleep(0.5) 
print(testresult) 
9
from subprocess import Popen, PIPE 
from tempfile import SpooledTemporaryFile as tempfile 
f = tempfile() 
f.write('one\ntwo\nthree\nfour\nfive\nsix\n') 
f.seek(0) 
print Popen(['/bin/grep','f'],stdout=PIPE,stdin=f).stdout.read() 
f.close() 
+3

fyi, tempfile.SpooledTemporaryFile .__ doc__ says: Contenedor temporal de archivos, especializado para cambiar de StringIO a un archivo real cuando excede un determinado tamaño o cuando se necesita un archivo. –

6
""" 
Ex: Dialog (2-way) with a Popen() 
""" 

p = subprocess.Popen('Your Command Here', 
       stdout=subprocess.PIPE, 
       stderr=subprocess.STDOUT, 
       stdin=PIPE, 
       shell=True, 
       bufsize=0) 
p.stdin.write('START\n') 
out = p.stdout.readline() 
while out: 
    line = out 
    line = line.rstrip("\n") 

    if "WHATEVER1" in line: 
     pr = 1 
     p.stdin.write('DO 1\n') 
     out = p.stdout.readline() 
     continue 

    if "WHATEVER2" in line: 
     pr = 2 
     p.stdin.write('DO 2\n') 
     out = p.stdout.readline() 
     continue 
""" 
.......... 
""" 

out = p.stdout.readline() 

p.wait() 
+3

Como 'shell = True' se usa tan comúnmente sin una buena razón, y esta es una pregunta popular, permítanme señalar que hay muchas situaciones en las que' Popen (['cmd', 'with', 'args' ]) es decididamente mejor que 'Popen ('cmd con args', shell = True)' y haciendo que el shell rompa el comando y los argumentos en tokens, pero no proporciona nada útil, mientras que agrega una cantidad significativa de complejidad y por lo tanto también superficie de ataque – tripleee

5

Mira que Popen.communicate(input=s) le puede dar problemas si s es demasiado grande, porque al parecer el proceso padre amortiguará que antes bifurcar el subproceso niño, lo que significa que necesita "dos veces más" que se utiliza la memoria en ese momento (al menos de acuerdo con la explicación "debajo del capó" y la documentación vinculada encontró here). En mi caso particular, s era un generador que fue primero completamente expandido y sólo entonces escrito a stdin por lo que el proceso padre era enorme justo antes de que el niño fue engendrado, y sin memoria fue dejado al tenedor que:

File "/opt/local/stow/python-2.7.2/lib/python2.7/subprocess.py", line 1130, in _execute_child self.pid = os.fork() OSError: [Errno 12] Cannot allocate memory

14

estoy usando python3 y descubrí que lo que necesita para codificar la cadena antes de pasarlo a la entrada estándar:

p = Popen(['grep', 'f'], stdout=PIPE, stdin=PIPE, stderr=PIPE) 
out, err = p.communicate(input='one\ntwo\nthree\nfour\nfive\nsix\n'.encode()) 
print(out) 
+4

No necesita codificar específicamente la entrada, solo quiere un objeto similar a un byte (por ejemplo, 'b'something''). Devolverá err y out también como bytes. Si desea evitar esto, puede pasar 'universal_newlines = True' a' Popen'. Entonces aceptará la entrada como str y devolverá err/out como str también. – Six

+2

Pero tenga cuidado, 'universal_newlines = True' también convertirá sus nuevas líneas para que coincida con su sistema – Nacht

+1

Si está utilizando Python 3, consulte [mi respuesta] (http://stackoverflow.com/a/41036665/247696) para obtener un resultado uniforme. una solución más conveniente. – Flimm

17

estoy un poco sorprendido de que nadie sugirió la creación de una tubería, que es en mi opinión el extremo forma más sencilla de pasar una cadena a stdin de un subproceso:

read, write = os.pipe() 
os.write(write, "stdin input here") 
os.close(write) 

subprocess.check_call(['your-command'], stdin=read) 
+1

Tanto la documentación del '' os' como la del 'subproceso' coinciden en que usted debe preferir esta última a la anterior. Esta es una solución heredada que tiene un reemplazo estándar (un poco menos conciso); la respuesta aceptada cita la documentación pertinente. – tripleee

+1

No estoy seguro de que sea correcto, tripleee.La documentación citada explica por qué es difícil utilizar las tuberías creadas por el proceso, pero en esta solución crea una tubería y la transfiere. Creo que evita los posibles problemas de interbloqueo de la gestión de las tuberías una vez que el proceso ya ha comenzado. –

+0

os.popen está en desuso en favor del subproceso – hd1

9

Existe una hermosa solución si usa Python 3.4 o superior. Utilice el argumento input en lugar del argumento stdin, que acepta un argumento de bytes:

output = subprocess.check_output(
    ["sed", "s/foo/bar/"], 
    input=b"foo", 
) 
+0

no funciona para 'call' – vidstige

+1

@vidstige Tiene razón, eso es extraño. Consideraría presentar esto como un error de Python, no veo ninguna buena razón para que 'check_output' tenga un argumento' input', pero no 'call'. – Flimm

Cuestiones relacionadas