web-dev-qa-db-fra.com

Python - Comment passer une chaîne dans subprocess.Popen (en utilisant l'argument stdin)?

Si je fais ce qui suit:

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

Je reçois:

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'

Apparemment, un objet cStringIO.StringIO ne coche pas assez près d'un fichier canard pour convenir à subprocess.Popen. Comment est-ce que je travaille autour de ceci?

258
Daryl Spitzer

Popen.communicate() documentation:

Notez que si vous souhaitez envoyer des données au stdin du processus, vous devez créer l’objet Popen avec stdin = PIPE. De même, pour obtenir autre chose que Aucun dans le résultat Tuple, vous devez également donner stdout = PIPE et/ou stderr = PIPE.

Remplacement de os.popen *

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

Warning Utilisez communic () plutôt que stdin.write (), stdout.read () ou stderr.read () pour éviter les blocages dus à l'un des d’autres tampons de tuyau OS remplissant et bloquant le processus enfant.

Donc, votre exemple pourrait être écrit comme suit:

_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
# ->
_

Sur la version actuelle de Python 3, vous pouvez utiliser subprocess.run , pour passer l'entrée sous forme de chaîne à une commande externe et obtenir son état de sortie et sa sortie en tant que chaîne en un appel:

_#!/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
# -> 
_
306
jfs

J'ai compris cette solution de contournement:

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

Y en a t-il un meilleur?

43
Daryl Spitzer

Je suis un peu surpris que personne n'ait suggéré de créer un tuyau, ce qui est à mon avis le moyen le plus simple de passer une chaîne à stdin d'un sous-processus:

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

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

Il existe une solution intéressante si vous utilisez Python 3.4 ou supérieur. Utilisez l'argument input à la place de l'argument stdin, qui accepte un argument d'octets:

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

J'utilise python3 et j'ai découvert que vous deviez encoder votre chaîne avant de pouvoir la passer en 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)
14
qed

"Apparemment, un objet cStringIO.StringIO ne se rapproche pas suffisamment d'un fichier canard pour convenir à subprocess.Popen"

:-)

J'ai bien peur que non. Le canal est un concept de système d'exploitation de bas niveau, il nécessite donc absolument un objet fichier représenté par un descripteur de fichier au niveau du système d'exploitation. Votre solution de contournement est la bonne.

13
Dan Lenski
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()
7
Michael Waddell
"""
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()
6
Lucien Hercaud

Notez que Popen.communicate(input=s) peut vous causer des ennuis sissi est trop gros, car apparemment, le processus parent le mettra en mémoire tampon avant forçant le sous-processus enfant, ce qui signifie qu'il a besoin " deux fois plus de "mémoire utilisée à ce moment-là (du moins selon l'explication" sous le capot "et la documentation associée trouvée ici ). Dans mon cas particulier, s était un générateur qui a d'abord été complètement développé et ensuite écrit dans stdin de sorte que le processus parent était énorme juste avant la génération de l'enfant, et qu'il ne restait plus de mémoire pour le fourrer:

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

5
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)
3
gedwarp