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

280

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 s'active pas assez près d'un canard de fichier pour convenir à subprocess.Popen. Comment contourner cela?

Daryl Spitzer
la source
3
Au lieu de contester ma réponse avec cette suppression, je l'ajoute en tant que commentaire ... Lecture recommandée: Article de blog du module Python de la semaine de Doug Hellmann sur le sous-processus .
Daryl Spitzer
3
l'article de blog contient plusieurs erreurs, par exemple, le tout premier exemple de code:call(['ls', '-1'], shell=True) est incorrect. Je recommande plutôt de lire les questions courantes de la description des balises du sous-processus . En particulier, pourquoi subprocess.Popen ne fonctionne pas lorsque args est une séquence? explique pourquoi a call(['ls', '-1'], shell=True)tort. Je me souviens d'avoir laissé des commentaires sous le blog, mais je ne les vois pas maintenant pour une raison quelconque.
jfs
Pour les plus récents, subprocess.runvoir stackoverflow.com/questions/48752152/…
Boris

Réponses:

326

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 None dans le tuple de résultat, vous devez également indiquer 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

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

Ainsi, 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 sous forme de chaîne 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
# -> 
jfs
la source
3
J'ai raté cet avertissement. Je suis content d'avoir demandé (même si je pensais avoir la réponse).
Daryl Spitzer
11
Ce n'est PAS une bonne solution. En particulier, vous ne pouvez pas traiter de manière asynchrone la sortie p.stdout.readline si vous le faites car vous devrez attendre que la sortie standard complète arrive. Il est également inefficace en mémoire.
OTZ
7
@OTZ Quelle est la meilleure solution?
Nick T
11
@Nick T: " mieux " dépend du contexte. Les lois de Newton sont bonnes pour le domaine dans lequel elles s'appliquent, mais vous avez besoin d'une relativité particulière pour concevoir un GPS. Voir Lecture non bloquante sur un sous-processus.PIPE en python .
jfs
9
Mais notez la REMARQUE pour communiquer : "n'utilisez pas cette méthode si la taille des données est grande ou illimitée"
Owen
44

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?

Daryl Spitzer
la source
25
@Moe: l' stdin.write()utilisation est déconseillée, p.communicate()doit être utilisée. Voir ma réponse.
jfs
11
Selon la documentation du sous-processus: Avertissement - Utilisez Communiquer () plutôt que .stdin.write, .stdout.read ou .stderr.read pour éviter les blocages dus à l'un des autres tampons de tuyaux du système d'exploitation remplissant et bloquant le processus enfant.
Jason Mock
1
Je pense que c'est une bonne façon de le 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.
Lucretiel
1
En particulier, le faire de cette façon garantit toujours que stdin est fermé, de sorte que si les sous-processus sont ceux qui consomment des entrées pour toujours, le communicatefermera le canal et permettra au processus de se terminer normalement.
Lucretiel
@Lucretiel, si le processus consomme stdin pour toujours, alors il peut vraisemblablement écrire stdout pour toujours, nous aurions donc besoin de techniques complètement différentes à tous les niveaux (impossible à read()partir de cela, comme communicate()même sans argument).
Charles Duffy
25

Je suis un peu surpris que personne n'ait suggéré de créer un pipe, 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)
Graham Christensen
la source
2
Le oset la subprocessdocumentation conviennent tous deux que vous devriez préférer ce dernier au premier. Il s'agit d'une solution héritée qui a un remplacement standard (légèrement moins concis); la réponse acceptée cite la documentation pertinente.
tripleee
1
Je ne suis pas sûr que ce soit correct, tripleee. La documentation citée explique pourquoi il est difficile d'utiliser les tuyaux créés par le processus, mais dans cette solution, il crée un tuyau et le transmet. Je pense que cela évite les problèmes de blocage potentiels de la gestion des tuyaux après le début du processus.
Graham Christensen
os.popen est déprécié au profit d'un sous
hd1
2
-1: cela conduit au blocage, il peut perdre des données. Cette fonctionnalité est déjà fournie par le module de sous-processus. Utilisez-le au lieu de le réimplémenter mal (essayez d'écrire une valeur plus grande qu'un tampon de pipe OS)
jfs
Vous méritez le meilleur homme bon, merci pour la solution la plus simple et la plus intelligente
Felipe Buccioni
21

Il existe une belle solution si vous utilisez Python 3.4 ou mieux. Utilisez l' inputargument au lieu de l' stdinargument, qui accepte un argument octets:

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

Cela fonctionne pour check_outputet run, mais pas callou check_callpour une raison quelconque.

Flimm
la source
5
@vidstige Vous avez raison, c'est bizarre. J'envisagerais de classer cela comme un bug Python, je ne vois aucune bonne raison pour laquelle check_outputdevrait avoir un inputargument, mais pas call.
Flimm
2
C'est la meilleure réponse pour Python 3.4+ (en l'utilisant dans Python 3.6). En effet, cela ne fonctionne pas avec check_callmais cela fonctionne pour run. Il fonctionne également avec input = string tant que vous passez également un argument de codage selon la documentation.
Nikolaos Georgiou
13

J'utilise python3 et j'ai découvert que vous devez encoder votre chaîne 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)
qed
la source
5
Vous n'avez pas spécifiquement besoin de coder l'entrée, elle veut juste un objet de type octets (par exemple b'something'). Il renverra également err et out en octets. Si vous voulez éviter cela, vous pouvez passer universal_newlines=Trueà Popen. Ensuite, il acceptera l'entrée comme str et renverra également err / out comme str.
six
2
Mais attention, universal_newlines=Trueconvertira également vos nouvelles lignes en fonction de votre système
Nacht
1
Si vous utilisez Python 3, consultez ma réponse pour une solution encore plus pratique.
Flimm
12

Apparemment, un objet cStringIO.StringIO ne s'active pas assez près d'un canard de fichier pour convenir au sous-processus.

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 qui est représenté par un descripteur de fichier de niveau système d'exploitation. Votre solution de contournement est la bonne.

Dan Lenski
la source
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
la source
3
fyi, tempfile.SpooledTemporaryFile .__ doc__ dit: wrapper de fichier temporaire, spécialisé pour passer de StringIO à un fichier réel quand il dépasse une certaine taille ou quand un fileno est nécessaire.
Doug F
5

Attention, cela Popen.communicate(input=s)peut vous poser des problèmes s'il sest trop volumineux, car apparemment le processus parent le tamponnera avant de bifurquer le sous-processus enfant, ce qui signifie qu'il a besoin de "deux fois plus" de mémoire utilisée à ce stade (au moins selon l'explication "sous le capot") et la documentation liée trouvée ici ). Dans mon cas particulier, sétait un générateur qui a d'abord été entièrement développé et ensuite seulement écrit, de stdinsorte que le processus parent était énorme juste avant la naissance de l'enfant, et aucune mémoire n'était laissée pour le bifurquer:

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
la source
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
la source
4
Parce qu'il shell=Trueest si couramment utilisé sans raison valable, et c'est une question populaire, permettez-moi de souligner qu'il y a beaucoup de situations où il Popen(['cmd', 'with', 'args'])est nettement préférable Popen('cmd with args', shell=True)et que le shell décompose la commande et les arguments en jetons, mais ne fournit rien d'autre utile, tout en ajoutant une quantité importante de complexité et donc aussi attaquer la surface.
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
la source
1

Sur Python 3.7+, procédez comme suit:

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

et vous voudrez probablement ajouter capture_output=Truepour obtenir la sortie de l'exécution de la commande sous forme de chaîne.

Sur les anciennes versions de Python, remplacez text=Truepar universal_newlines=True:

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