Beispiel für richtiges verwenden von PIPES

Python in C/C++ embedden, C-Module, ctypes, Cython, SWIG, SIP etc sind hier richtig.
Antworten
chrikle
User
Beiträge: 1
Registriert: Dienstag 29. November 2011, 14:05

Hallo,

ich möchte von Python ein externes Programm aufrufen. Das Python Programm soll dann auf die Ausgaben reagieren.
Prinzipiell würde ich das Programm so ansetzen:

Code: Alles auswählen

my_command="\n"
p = subprocess.Popen(["programm"], shell=False,  stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
while (1):            
            (stdoutdata, stderrdata) = p.communicate(my_command)
            lines = stdoutdata.split("\n")            
            errorlines = stderrdata.split("\n")
            for line in lines:
               if (line=="a"):
                  my_command="a\n"
               else (line=="b"):
                  my_command="q\n"
Da communicate nur einen Befehl piped, habe ich schon mit p.stdin.write herum experimentiert, das hat aber auch nicht funktioniert!
Hat jemand einen Tip oder ein Beispiel, wie man unter Python mit Pipes ein anderes Programm vernünftig steuern kann?
Meine bisherige suche im Internet war erfolglos!

Grüße
Chrikle
Dav1d
User
Beiträge: 1437
Registriert: Donnerstag 30. Juli 2009, 12:03
Kontaktdaten:

Kommandozeilen-Argumente kannst du nur einmal übergeben, des Weiteren auch nur ein Programm je Popen Aufruf starten, allerdings kannst du dein gestartetes Programm mit Daten über stdin versorgen, wenn das nur einmal geschehen soll, dann ist .communicate genau das Richtige. Ansonsten musst du wohl oder übel nach .stdin schreiben und gleichzeitig von stdout lesen (wenn du die "Antwort" sofort erhalten willst). Unter Linux/Unix kannst du dafür das select Modul nutzen, unter Windows wird dir nicht viel anderes übrig bleiben, als das Lesen von stdout in einen Thread zu verlagern, da sonst der Programmfluss unterbrochen wird. Generell würde ich von stdout mit .readline() lesen, natürlich könntest du auch .read(bytes) verwenden. (für stderr gilt natürlich das Gleiche wie für stdout)
the more they change the more they stay the same
deets

Ich war auch kurz davor, so eine Antwort zu schreiben. Aber irgendwie stimmt die Theorie nicht mit meiner Praxis ueberein:

Code: Alles auswählen


import sys
import time
from functools import partial
import subprocess, threading


p = subprocess.Popen(["cat", "-u"], stdin=subprocess.PIPE, stdout=subprocess.PIPE, shell=False, bufsize=0)

## print p.communicate("foobarbaz")

## sys.exit()


def run(p):
    while True:
        input_ = p.stdout.read()
        if input_:
            print "read input: %s" % input_
        else:
            break


reader = threading.Thread(target=partial(run, p))
reader.start()


while True:
    print "writing"
    p.stdin.write("hallo\n")
    p.stdin.flush()
Funktioniert nur insofern, als das bei druecken von C-c die gesamten hallos ausgegeben werden - sprich, irgendwas buffert. Kann natuerlich sein, dass cat da komisch ist - aber auch das habe ich mit einem simplen python-script versucht zu umgehen, welches einfach sys.stdin auf sys.stdout ausgibt - auch nix.
BlackJack

@deets: ``cat`` wird seine Ausgabe puffern. Und das machen 99% der anderen Programme auch, weil dass das normale Verhalten ist, wenn man nicht explizit etwas anderes sagt oder `flush()`\t. Wenn man also das aufgerufene Programm nicht dazu bringen kann nicht zu puffern, sind Pipes keine Lösung. Dann muss man so etwas wie `pexpect` verwenden was dem aufgerufenen Programm vorspiegelt es würde nicht über Pipes sondern über ein Terminal kommunizieren. Da ist das normale Verhalten von 99% der Programme, das zeilenweise gepuffert wird.
deets

@BlackJack

Klar, habe ich auch gedacht. Darum das "-u"-Argument, welches das eigentlich verhindern sollte. Als das auch nix fruchtete, habe ich statt cat ein foo.py genommen, welches

Code: Alles auswählen

while True:
    sys.stdout.write(sys.stdin.read())
    sys.stdout.flush()
gemacht hat. Keine Aenderung. Genausowenig, wenn ich darin das read entfernt & zu einem konstanten Text gemacht habe. :K
lunar

@deets: "p.stdout.read()" liest laut Dokumentation immer bis zum Ende des Datenstroms, unabhängig von irgendwelchen Puffern:
Read at most size bytes from the file (less if the read hits EOF before obtaining size bytes). If the size argument is negative or omitted, read all data until EOF is reached. […]
"p.stdout.read()" wartet also nicht, bis die Puffer voll sind, sondern bis "cat" seine Standardausgabe schließt. In Deinem Beispiel ist das nie der Fall, da es "cat" weder terminiert noch dessen Standardeingabe schließt. "cat" wird daher erst beendet, wenn es von außen unterbrochen wird, in Deinem Fall über "Strg+C". Dann schließt es die Standardausgabe, ".read()" kehrt zurück, und die gesamte Eingabe wird wieder ausgegeben.

Das gilt so natürlich auch für das zweite Beispiel, indem – wenn ich Dich richtig verstehe – einfach nur "cat" durch ein eigenes Testskript "foo.py" ersetzt wurde, aber der Teil, welcher den Prozess ausführt, unverändert belassen wurde.

Die Lösung ist, ".read()" mit der Anzahl der erwarteten Bytes aufzurufen, im Zweifelsfall halt ".read(1)". Ich vermute, dass dann zumindest das zweite Beispiel mit "foo.py" funktioniert. Ob "cat" auch funktioniert, weiß ich nicht. "cat -u" ist jedenfalls wohl dasselbe wie "cat", zumindest sagt "cat --help" auf meinem System (Fedora 15 mit coreutils 8.12), dass "-u" einfach ignoriert wird. "cat" puffert seine Ausgabe also wohl einfach immer.
deets

@lunar

*facepalm*

Klar. Man sollte keine calls verwenden, die EOF erwarten. Und zwar weder im Kind, noch im Parent..... Danke, damit klappt's dann. Und zwar auch mit cat -u, zumindest unter OSX

Code: Alles auswählen


import sys
import time
from functools import partial
import subprocess, threading


p = subprocess.Popen(["cat", "-u"], stdin=subprocess.PIPE, stdout=subprocess.PIPE, shell=False, bufsize=0)


def run(p):
    while True:
        input_ = p.stdout.readline()
        if input_:
            print "read input: %s" % input_
        else:
            break


reader = threading.Thread(target=partial(run, p))
reader.start()


try:
    while True:
        p.stdin.write("hallo\n")
        p.stdin.flush()
except KeyboardInterrupt:
    pass

p.stdin.close()

reader.join()
EyDu
User
Beiträge: 4881
Registriert: Donnerstag 20. Juli 2006, 23:06
Wohnort: Berlin

@deets: Da geht aber noch was.

Code: Alles auswählen

def run(p):
    for input in iter(p.stdout.readline, ""):
        print "read input: %s" % input
Das Leben ist wie ein Tennisball.
ProGammler88
User
Beiträge: 13
Registriert: Dienstag 6. Dezember 2011, 15:01

bei mir funzt jetz alles hat ja echt ähnlichkeit mit java :D
BlackJack

@ProGammler88: Wie meinen!?
Liffi
User
Beiträge: 153
Registriert: Montag 1. Januar 2007, 17:23

BlackJack hat geschrieben:@ProGammler88: Wie meinen!?
if und else und import.
Leonidas
Python-Forum Veteran
Beiträge: 16025
Registriert: Freitag 20. Juni 2003, 16:30
Kontaktdaten:

Also import funktioniert ja mal völlig unterschiedlich im Vergleich zu Java, weil Java keinen Import in Python-Sinne hat sondern durch import eigentlich nur Aliase erstellt, damit man nicht so viel tippen muss.
My god, it's full of CARs! | Leonidasvoice vs (former) Modvoice
Antworten