Wie übergebe ich einen String an subprocess.Popen (mit dem Argument stdin)?

280

Wenn ich folgendes mache:

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

Ich bekomme:

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'

Anscheinend quakt ein cStringIO.StringIO-Objekt nicht nahe genug an einer Dateiente, um dem Unterprozess zu entsprechen. Wie kann ich das umgehen?

Daryl Spitzer
quelle
3
Anstatt meine Antwort mit dem Löschen zu bestreiten, füge ich sie als Kommentar hinzu ... Empfohlene Lektüre: Doug Hellmanns Blogbeitrag zum Python-Modul der Woche zum Unterprozess .
Daryl Spitzer
3
Der Blog-Beitrag enthält mehrere Fehler, z. B. das allererste Codebeispiel:call(['ls', '-1'], shell=True) ist falsch. Ich empfehle stattdessen, häufig gestellte Fragen aus der Tag-Beschreibung des Unterprozesses zu lesen . Insbesondere, warum subprocess.Popen nicht funktioniert, wenn args eine Sequenz ist? erklärt warum call(['ls', '-1'], shell=True)ist falsch. Ich erinnere mich, dass ich Kommentare unter dem Blog-Beitrag hinterlassen habe, aber ich sehe sie jetzt aus irgendeinem Grund nicht.
JFS
Für die neueren subprocess.runsiehe stackoverflow.com/questions/48752152/…
Boris

Antworten:

326

Popen.communicate() Dokumentation:

Beachten Sie, dass Sie das Popen-Objekt mit stdin = PIPE erstellen müssen, wenn Sie Daten an das stdin des Prozesses senden möchten. Um etwas anderes als None im Ergebnistupel zu erhalten, müssen Sie ebenfalls stdout = PIPE und / oder stderr = PIPE angeben.

Os.popen ersetzen *

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

Warnung Verwenden Sie communic () anstelle von stdin.write (), stdout.read () oder stderr.read (), um Deadlocks zu vermeiden, da andere OS-Pipe-Puffer den untergeordneten Prozess füllen und blockieren.

Ihr Beispiel könnte also wie folgt geschrieben werden:

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

In der aktuellen Python 3-Version können Sie die subprocess.runEingabe als Zeichenfolge an einen externen Befehl übergeben und den Beendigungsstatus sowie die Ausgabe als Zeichenfolge in einem Aufruf zurückerhalten:

#!/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
quelle
3
Ich habe diese Warnung verpasst. Ich bin froh, dass ich gefragt habe (obwohl ich dachte, ich hätte die Antwort).
Daryl Spitzer
11
Dies ist keine gute Lösung. Insbesondere können Sie die Ausgabe von p.stdout.readline nicht asynchron verarbeiten, wenn Sie dies tun, da Sie auf das Eintreffen des gesamten stdout warten müssten. Es ist auch speichereffizient.
OTZ
7
@OTZ Was ist eine bessere Lösung?
Nick T
11
@ Nick T: " besser " hängt vom Kontext ab. Newtons Gesetze sind gut für die Domäne, für die sie gelten, aber Sie benötigen eine spezielle Relativitätstheorie, um GPS zu entwerfen. Siehe Nicht blockierendes Lesen eines Unterprozesses . PIPE in Python .
JFS
9
Beachten Sie jedoch den HINWEIS für die Kommunikation : "Verwenden Sie diese Methode nicht, wenn die Datengröße groß oder unbegrenzt ist"
Owen
44

Ich habe diese Problemumgehung herausgefunden:

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

Gibt es einen besseren?

Daryl Spitzer
quelle
25
@Moe: stdin.write()Verwendung wird nicht empfohlen , p.communicate()sollte verwendet werden. Siehe meine Antwort.
JFS
11
Gemäß der Dokumentation des Unterprozesses: Warnung - Verwenden Sie "communic" () anstelle von .stdin.write, .stdout.read oder .stderr.read, um Deadlocks zu vermeiden, da andere OS-Pipe-Puffer den untergeordneten Prozess füllen und blockieren.
Jason Mock
1
Ich denke, dies ist ein guter Weg, wenn Sie sicher sind, dass Ihr Standard / Fehler niemals voll wird (zum Beispiel, wenn er in eine Datei geht oder ein anderer Thread ihn frisst) und Sie eine unbegrenzte Datenmenge haben an stdin geschickt werden.
Lucretiel
1
Wenn Sie dies auf diese Weise tun, wird insbesondere weiterhin sichergestellt, dass stdin geschlossen ist. Wenn der Unterprozess für immer Eingaben verbraucht, communicatewird die Pipe geschlossen und der Prozess kann ordnungsgemäß beendet werden.
Lucretiel
@Lucretiel, wenn der Prozess stdin für immer verbraucht, kann er vermutlich immer noch stdout für immer schreiben, sodass wir rundum völlig andere Techniken benötigen würden (kann nicht read()davon, wie communicate()auch ohne Argumente).
Charles Duffy
24

Ich bin ein bisschen überrascht, dass niemand vorgeschlagen hat, eine Pipe zu erstellen. Dies ist meiner Meinung nach der einfachste Weg, eine Zeichenfolge an stdin eines Unterprozesses zu übergeben:

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

subprocess.check_call(['your-command'], stdin=read)
Graham Christensen
quelle
2
Sowohl die osals auch die subprocessDokumentation stimmen darin überein, dass Sie die letztere der ersteren vorziehen sollten. Dies ist eine Legacy-Lösung mit einem (etwas weniger präzisen) Standardersatz. Die akzeptierte Antwort zitiert die einschlägige Dokumentation.
Tripleee
1
Ich bin mir nicht sicher, ob das richtig ist, Tripleee. In der zitierten Dokumentation wird angegeben, warum es schwierig ist, die durch den Prozess erstellten Pipes zu verwenden. In dieser Lösung wird jedoch ein Rohr erstellt und übergeben. Ich glaube, es vermeidet die potenziellen Deadlock-Probleme bei der Verwaltung der Pipes, nachdem der Prozess bereits gestartet wurde.
Graham Christensen
os.popen ist zugunsten des Teilprozesses veraltet
hd1
2
-1: Es führt zum Deadlock, es können Daten verloren gehen. Diese Funktionalität wird bereits vom Unterprozessmodul bereitgestellt. Verwenden Sie es, anstatt es schlecht neu zu implementieren (versuchen Sie, einen Wert zu schreiben, der größer als ein OS-Pipe-Puffer ist)
jfs
Sie verdienen den besten guten Mann, danke für die einfachste und klügste Lösung
Felipe Buccioni
21

Es gibt eine schöne Lösung, wenn Sie Python 3.4 oder besser verwenden. Verwenden Sie das inputArgument anstelle des stdinArguments, das ein Byte-Argument akzeptiert:

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

Dies funktioniert für check_outputund run, aber nicht calloder check_callaus irgendeinem Grund.

Flimm
quelle
5
@vidstige Du hast recht, das ist komisch. Ich würde in Betracht ziehen, dies als Python-Fehler einzureichen. Ich sehe keinen guten Grund darin, warum check_outputich ein inputArgument haben sollte, aber nicht call.
Flimm
2
Dies ist die beste Antwort für Python 3.4+ (in Python 3.6). Es funktioniert zwar nicht mit, check_callaber es funktioniert für run. Es funktioniert auch mit input = string, solange Sie gemäß der Dokumentation auch ein Codierungsargument übergeben.
Nikolaos Georgiou
13

Ich verwende python3 und habe herausgefunden, dass Sie Ihren String codieren müssen, bevor Sie ihn an stdin übergeben können:

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
quelle
5
Sie müssen die Eingabe nicht speziell codieren, sondern nur ein byteähnliches Objekt (z b'something'. B. ). Es wird err und out auch als Bytes zurückgeben. Wenn Sie dies vermeiden möchten, können Sie universal_newlines=Truean übergeben Popen. Dann akzeptiert es die Eingabe als str und gibt err / out auch als str zurück.
Sechs
2
Aber Vorsicht, universal_newlines=Truekonvertieren Sie auch Ihre Zeilenumbrüche, um sie an Ihr System anzupassen
Nacht - Reinstate Monica
1
Wenn Sie Python 3 verwenden, finden Sie in meiner Antwort eine noch bequemere Lösung.
Flimm
12

Anscheinend quakt ein cStringIO.StringIO-Objekt nicht nahe genug an einer Dateiente, um dem Unterprozess zu entsprechen

Ich fürchte nein. Die Pipe ist ein Betriebssystemkonzept auf niedriger Ebene, daher ist unbedingt ein Dateiobjekt erforderlich, das durch einen Dateideskriptor auf Betriebssystemebene dargestellt wird. Ihre Problemumgehung ist die richtige.

Dan Lenski
quelle
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
quelle
3
fyi, tempfile.SpooledTemporaryFile .__ doc__ sagt: Temporärer Datei-Wrapper, der darauf spezialisiert ist, von StringIO zu einer realen Datei zu wechseln, wenn diese eine bestimmte Größe überschreitet oder wenn ein Fileno benötigt wird.
Doug F
5

Beachten Sie, dass dies zu Popen.communicate(input=s)Problemen führen kann, wenn ses zu groß ist, da der übergeordnete Prozess es anscheinend puffert, bevor der untergeordnete Unterprozess gegabelt wird. Dies bedeutet, dass zu diesem Zeitpunkt "doppelt so viel" Speicher benötigt wird (zumindest gemäß der Erklärung "unter der Haube") und verknüpfte Dokumentation finden Sie hier ). In meinem speziellen Fall handelte ses sich um einen Generator, der zuerst vollständig erweitert und erst dann beschrieben wurde, stdinsodass der übergeordnete Prozess unmittelbar vor dem Laichen des Kindes sehr umfangreich war und kein Speicher mehr vorhanden war, um ihn zu teilen:

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
quelle
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
quelle
4
Da dies shell=Trueso häufig ohne guten Grund verwendet wird und dies eine beliebte Frage ist, möchte ich darauf hinweisen, dass es viele Situationen gibt, in denen Popen(['cmd', 'with', 'args'])es entschieden besser ist als Popen('cmd with args', shell=True)und wenn die Shell den Befehl und die Argumente in Token zerlegt, aber ansonsten nichts bereitstellt nützlich, während eine erhebliche Menge an Komplexität hinzugefügt wird und somit auch die Oberfläche angreift.
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
quelle
1

Gehen Sie unter Python 3.7+ folgendermaßen vor:

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

und Sie möchten wahrscheinlich hinzufügen capture_output=True, um die Ausgabe des Ausführens des Befehls als Zeichenfolge zu erhalten.

Ersetzen Sie in älteren Versionen von Python Folgendes text=Truedurch universal_newlines=True:

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