Python - Comment passer une chaîne dans subprocess.Popen (en utilisant l'argument stdin)?
-
03-07-2019 - |
Question
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 se rapproche pas suffisamment d'un fichier canard pour convenir à subprocess.Popen. Comment puis-je contourner ce problème?
La solution
Popen.communicate ()
documentation:
Notez que si vous souhaitez envoyer des données à processus stdin, vous devez créer l'objet Popen avec stdin = PIPE. De même, pour obtenir quelque chose autre que Aucun dans le tuple de résultat, vous devez donner stdout = PIPE et / ou stderr = PIPE aussi.
Remplacement de os.popen *
pipe = os.popen(cmd, 'w', bufsize)
# ==>
pipe = Popen(cmd, shell=True, bufsize=bufsize, stdin=PIPE).stdin
Avertissement : utilisez communic () plutôt que stdin.write (), stdout.read () ou stderr.read () pour éviter les blocages dus à l'un des autres tampons de tuyaux OS remplir et bloquer l'enfant processus.
Votre exemple pourrait donc ê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
# ->
Dans la version actuelle de Python 3, vous pouvez utiliser le sous-processus .run
, pour passer l’entrée sous forme de chaîne à une commande externe et obtenir son état de sortie et sa sortie sous forme de chaîne lors d’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
# ->
Autres conseils
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 a-t-il un meilleur?
Je suis un peu surpris que personne n'ait suggéré de créer un canal, 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)
Il existe une solution intéressante si vous utilisez Python version 3.4 ou supérieure. Utilisez l'argument input
au lieu de l'argument stdin
, qui accepte un argument d'octets:
output = subprocess.check_output(
["sed", "s/foo/bar/"],
input=b"foo",
)
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)
"Apparemment, un objet cStringIO.StringIO n'est pas assez proche 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.
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()
"""
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()
Notez que Popen.communicate (input = s)
peut vous causer des problèmes si s
est trop volumineux, car apparemment, le processus parent le mettra en mémoire tampon avant forger le sous-processus enfant, ce qui signifie qu'il a besoin de "deux fois plus". utilisé la mémoire à ce moment-là (au moins selon l'explication "sous le capot" et la documentation associée trouvées ici ). Dans mon cas particulier, s
était un générateur qui a été complètement développé et écrit ensuite dans stdin
, de sorte que le processus parent était énorme juste avant que l'enfant ne soit créé,
et il ne restait plus de mémoire pour le bifurquer:
Fichier "/opt/local/stow/python-2.7.2/lib/python2.7/subprocess.py" ;, ligne 1130, dans _execute_child
self.pid = os.fork ()
OSError: [Errno 12] Impossible d'allouer de la mémoire
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)