¿Cómo paso una cadena en subprocess.Popen (usando el argumento stdin)?

280

Si hago lo siguiente:

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

Yo obtengo:

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'

Aparentemente, un objeto cStringIO.StringIO no se acerca lo suficientemente cerca de un archivo duck como para adaptarse al subproceso. ¿Cómo evito esto?

Daryl Spitzer
fuente
3
En lugar de disputar mi respuesta con esta eliminación, la agrego como un comentario ... Lectura recomendada: publicación del blog del Módulo Python de la Semana de Doug Hellmann sobre subproceso .
Daryl Spitzer
3
la publicación del blog contiene múltiples errores, por ejemplo, el primer ejemplo de código:call(['ls', '-1'], shell=True) es incorrecto. En su lugar, recomiendo leer preguntas comunes de la descripción de la etiqueta del subproceso . En particular, ¿Por qué el subproceso.Popen no funciona cuando args es secuencia? explica por qué call(['ls', '-1'], shell=True)está mal Recuerdo haber dejado comentarios debajo de la publicación del blog, pero no los veo ahora por alguna razón.
jfs
Para los más nuevos, subprocess.runconsulte stackoverflow.com/questions/48752152/…
Boris el

Respuestas:

326

Popen.communicate() documentación:

Tenga en cuenta que si desea enviar datos al stdin del proceso, debe crear el objeto Popen con stdin = PIPE. Del mismo modo, para obtener algo más que Ninguno en la tupla de resultados, debe proporcionar stdout = PIPE y / o stderr = PIPE también.

Sustitución de os.popen *

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

Advertencia Utilice comunicar () en lugar de stdin.write (), stdout.read () o stderr.read () para evitar puntos muertos debido a que cualquiera de los otros buffers de tuberías del sistema operativo se llenan y bloquean el proceso secundario.

Entonces su ejemplo podría escribirse 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, puede usar subprocess.run, para pasar la 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
# -> 
jfs
fuente
3
Perdí esa advertencia. Me alegro de haber preguntado (aunque pensé que tenía la respuesta).
Daryl Spitzer
11
Esta NO es una buena solución. En particular, no puede procesar de forma asincrónica la salida de p.stdout.readline si lo hace, ya que tendría que esperar a que llegue todo el stdout. También es ineficiente de memoria.
OTZ
77
@OTZ ¿Qué es una mejor solución?
Nick T
11
@Nick T: " mejor " depende del contexto. Las leyes de Newton son buenas para el dominio en el que son aplicables, pero necesita una relatividad especial para diseñar el GPS. Consulte Lectura sin bloqueo en un subproceso . PIPE en python .
jfs
9
Pero tenga en cuenta la NOTA para comunicarse : "no use este método si el tamaño de los datos es grande o ilimitado"
Owen
44

Descubrí esta solución alternativa:

>>> 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 alguno mejor?

Daryl Spitzer
fuente
25
@Moe: stdin.write()se desaconseja el uso, p.communicate()debe usarse. Mira mi respuesta.
jfs
11
De acuerdo con la documentación del subproceso: Advertencia: utilice comunicar () en lugar de .stdin.write, .stdout.read o .stderr.read para evitar puntos muertos debido a que cualquiera de los otros buffers de tuberías del sistema operativo llenan y bloquean el proceso secundario.
Jason Mock
1
Creo que esta es una buena manera de hacerlo si está seguro de que su stdout / err nunca se llenará (por ejemplo, va a un archivo u otro hilo lo está comiendo) y tiene una cantidad ilimitada de datos para ser enviado a stdin.
Lucretiel
1
En particular, hacerlo de esta manera todavía asegura que el stdin esté cerrado, de modo que si los subprocesos consumen información para siempre, communicatecerrarán la tubería y permitirán que el proceso finalice con gracia.
Lucretiel
@Lucretiel, si el proceso consume stdin para siempre, entonces presumiblemente todavía puede escribir stdout para siempre, por lo que necesitaríamos técnicas completamente diferentes (no se puede read(), communicate()incluso sin argumentos).
Charles Duffy
25

Estoy un poco sorprendido de que nadie haya sugerido crear una tubería, que en mi opinión es la forma más simple 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)
Graham Christensen
fuente
2
La documentación osy la subprocessdocumentación coinciden en que debe preferir lo último a lo primero. 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 dice por qué es difícil usar las tuberías creadas por el proceso, pero en esta solución crea una tubería y la pasa. Creo que evita los posibles problemas de bloqueo de la administración de las tuberías después de que el proceso ya ha comenzado.
Graham Christensen
os.popen está en desuso a favor del subproceso
hd1
2
-1: lleva al punto muerto, puede perder datos. Esta funcionalidad ya la proporciona el módulo de subproceso. Úselo en lugar de volver a
implementarlo
Te mereces el mejor hombre bueno, gracias por la solución más simple e inteligente
Felipe Buccioni
21

Hay una solución hermosa si estás usando Python 3.4 o mejor. Use el inputargumento en lugar del stdinargumento, que acepta un argumento de bytes:

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

Esto funciona para check_outputy run, pero no callo check_callpor alguna razón.

Flimm
fuente
55
@vidstige Tienes razón, eso es raro. Consideraría presentar esto como un error de Python, no veo ninguna buena razón por la que check_outputdeba tener una inputdiscusión, pero no call.
Flimm
2
Esta es la mejor respuesta para Python 3.4+ (usándolo en Python 3.6). De hecho, no funciona check_callpero funciona run. También funciona con input = string siempre que pase un argumento de codificación también de acuerdo con la documentación.
Nikolaos Georgiou
13

Estoy usando python3 y descubrí que necesitas codificar tu cadena antes de poder pasarla a stdin:

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)
qed
fuente
55
No necesita codificar específicamente la entrada, solo quiere un objeto similar a bytes (por ejemplo b'something'). Devolverá err y out como bytes también. Si desea evitar esto, puede pasar universal_newlines=Truea Popen. Entonces aceptará la entrada como str y devolverá err / out como str también.
Seis
2
Pero tenga cuidado, universal_newlines=Truetambién convertirá sus nuevas líneas para que coincidan con su sistema
Nacht
1
Si está utilizando Python 3, vea mi respuesta para una solución aún más conveniente.
Flimm
12

Aparentemente, un objeto cStringIO.StringIO no se acerca demasiado a un pato de archivo como para adaptarse al subproceso.

Me temo que no. La canalización 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 es la correcta.

Dan Lenski
fuente
7
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()
Michael Waddell
fuente
3
fyi, tempfile.SpooledTemporaryFile .__ doc__ dice: Contenedor de archivos temporal, especializado para cambiar de StringIO a un archivo real cuando excede un cierto tamaño o cuando no se necesita un archivo.
Doug F
5

Tenga en cuenta que Popen.communicate(input=s)puede causarle problemas si ses demasiado grande, porque aparentemente el proceso principal lo amortiguará antes de bifurcar el subproceso secundario, lo que significa que necesita "el doble" de memoria usada en ese momento (al menos de acuerdo con la explicación "bajo el capó" y documentación vinculada que se encuentra aquí ). En mi caso particular, sfue un generador que primero se expandió por completo y solo luego se escribió para stdinque el proceso principal fuera enorme justo antes de que se generara el hijo, y no quedara memoria para bifurcarlo:

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

Lord Henry Wotton
fuente
5
"""
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()
Lucien Hercaud
fuente
44
Debido a shell=Trueque 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 with args', shell=True)hacer que el shell rompa el comando y los argumentos en tokens, pero no proporcione nada útil, al tiempo que agrega una cantidad significativa de complejidad y, por lo tanto, también ataca la superficie.
tripleee
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)
dusan
fuente
1

En Python 3.7+ haz esto:

my_data = "whatever you want\nshould match this f"
subprocess.run(["grep", "f"], text=True, input=my_data)

y probablemente desee agregar capture_output=Truepara obtener el resultado de ejecutar el comando como una cadena.

En versiones anteriores de Python, reemplace text=Truecon universal_newlines=True:

subprocess.run(["grep", "f"], universal_newlines=True, input=my_data)
Boris
fuente