321 votes

Comment passer une chaîne de caractères 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]

J'ai compris :

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 n'est pas assez proche d'un canard de fichier pour convenir à subprocess.Popen. Comment puis-je contourner ce problème ?

3 votes

Au lieu de contester ma réponse en la supprimant, je l'ajoute en commentaire... Lecture recommandée : L'article du blog de Doug Hellmann sur le module Python de la semaine concernant le sous-processus. .

3 votes

Le billet de blog contient de multiples erreurs, par exemple, le tout premier exemple de code : call(['ls', '-1'], shell=True) est incorrect. Je vous recommande de lire questions communes de la description du tag "subprocess à la place. En particulier, Pourquoi subprocess.Popen ne fonctionne pas lorsque args est une séquence ? explique pourquoi call(['ls', '-1'], shell=True) est erronée. Je me souviens avoir laissé des commentaires sous l'article du blog, mais je ne les vois plus maintenant pour une raison quelconque.

2 votes

Pour les plus récents subprocess.run voir stackoverflow.com/questions/48752152/

377voto

J.F. Sebastian Points 102961

Popen.communicate() documentation :

Notez que si vous voulez envoyer des données à le stdin du processus, vous devez créer l'objet Popen avec stdin=PIPE. De même, pour obtenir quelque chose autre que None dans le tuple de résultat, vous devez donner stdout=PIPE et/ou stderr=PIPE également.

Remplacer os.popen*

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

Avertissement Utilisez communicate() plutôt que stdin.write(), stdout.read() ou stderr.read() pour éviter des blocages dus à dus à l'un des autres tampons de tuyaux du système d'exploitation se remplissent et bloquent le processus enfant. enfant.

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

Sur Python 3.5+ (3.6+ pour les encoding ), vous pouvez utiliser subprocess.run pour passer une entrée sous forme de chaîne à une commande externe et obtenir son état de sortie, et sa sortie sous forme de chaîne en retour en un seul 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
# ->

3 votes

J'ai manqué cet avertissement. Je suis content d'avoir posé la question (même si je pensais avoir la réponse).

15 votes

Ce n'est PAS une bonne solution. En particulier, vous ne pouvez pas traiter de manière asynchrone la sortie de p.stdout.readline si vous faites cela, car vous devriez attendre l'arrivée de la totalité de stdout. C'est aussi une solution peu efficace en termes de mémoire.

8 votes

@OTZ Quelle est la meilleure solution ?

48voto

Daryl Spitzer Points 18304

J'ai trouvé une 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 une meilleure solution ?

27 votes

@Moe : stdin.write() est découragée, p.communicate() devrait être utilisé. Voir ma réponse.

12 votes

Selon la documentation sur les sous-processus : Avertissement - Utilisez communicate() plutôt que .stdin.write, .stdout.read ou .stderr.read pour éviter les blocages dus au fait que l'un des autres tampons de tuyaux du système d'exploitation se remplit et bloque le processus enfant.

2 votes

Je pense que c'est une bonne façon de faire si vous êtes sûr que votre stdout/err ne se remplira jamais (par exemple, il va dans un fichier, ou un autre thread le mange) et que vous avez une quantité illimitée de données à envoyer à stdin.

13voto

qed Points 1769

J'utilise python3 et j'ai découvert que vous devez encoder votre chaîne de caractères avant de pouvoir la passer dans 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)

5 votes

Vous n'avez pas besoin de coder l'entrée, il faut juste un objet de type octet (ex. b'something' ). Il retournera err et out sous forme d'octets également. Si vous voulez éviter cela, vous pouvez passer la commande universal_newlines=True à Popen . Alors il acceptera l'entrée comme str et retournera err/out comme str également.

2 votes

Mais attention, universal_newlines=True convertira également vos sauts de ligne pour qu'ils correspondent à votre système.

1 votes

Si vous utilisez Python 3, voir ma réponse pour une solution encore plus pratique.

12voto

Dan Points 18831

Apparemment, un objet cStringIO.StringIO n'est pas assez proche d'un canard de fichier pour convenir à subprocess. un canard de fichier pour convenir à subprocess.Popen

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

10voto

Michael Waddell Points 59
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 votes

Fyi, tempfile.SpooledTemporaryFile.__doc__ dit : Enveloppeur de fichier temporaire, spécialisé pour passer de StringIO à un vrai fichier quand il dépasse une certaine taille ou quand un fileno est nécessaire.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X