subprocess.call: Schreiben von stdout und stderr

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
Benutzeravatar
TheGrudge
User
Beiträge: 91
Registriert: Donnerstag 4. Mai 2006, 18:39

Donnerstag 14. Juli 2011, 12:13

Hallo,

ich würde gerne den Output eines Programms in ein File schreiben. Zusätzlich soll der Programmoutput aber auch in die Konsole geschrieben werden. Das Schreiben in ein File habe ich folgendermaßen gelöst:

Code: Alles auswählen

subprocess.call(arguments, shell=True, stdout=f, stderr=f)
wobei f ein Fileobject ist.

Nur sehe ich den Output nicht mehr in der Konsole. Kann ich den stdout an zwei verschiedene Objekte (File und Konsole) pipen?
Zuletzt geändert von TheGrudge am Donnerstag 14. Juli 2011, 19:19, insgesamt 2-mal geändert.
deets

Donnerstag 14. Juli 2011, 12:41

Du kannst ihn zB in ein cStringIO.StringIO-Objekt fliessen lassen & dann daraus lesen & in beide anderen Streams schreiben.
Benutzeravatar
TheGrudge
User
Beiträge: 91
Registriert: Donnerstag 4. Mai 2006, 18:39

Donnerstag 14. Juli 2011, 14:50

Hallo, danke für den Tipp. Das Problem ist das die Ausgabe des Programms mehrere tausend Zeilen beinhaltet und das Programm an sich schon eine Stunde läuft, da kann ich nicht alles zwischen cachen und danach erst herausschreiben.
Ich bräuchte eher so eine Art Monitoring vom Logfile. Ich hatte unter Windows das Skript einfach so laufen lassen (also nur in die Logdatei geschrieben) und dann WinTail starten lassen, welches mir das Logfile ausgegeben hat.

Nun soll das Skrtipt aber platformunabhängig sein, und daher dachte ich, es wäre möglich, die Ausgabe in die Console und in eine Datei zu leiten.
Benutzeravatar
cofi
Moderator
Beiträge: 4432
Registriert: Sonntag 30. März 2008, 04:16
Wohnort: RGFybXN0YWR0

Donnerstag 14. Juli 2011, 15:18

Schau mal ob `logging` das evtl bietet, ansonsten laesst sich eine `Tee` Klasse ganz einfach als Kontextmanager erstellen. Und das `write` von Tee schreibt eben in mehrere file-like Objekte, bzw Dateinamen.

Code: Alles auswählen

with Tee("log.file", sys.stdout) as tee:
    call(..., stdout=tee,...)
Evtl schreib ich heut abend noch ein wenig Code, wenn ich mehr Muße habe.
derdon
User
Beiträge: 1316
Registriert: Freitag 24. Oktober 2008, 14:32

Donnerstag 14. Juli 2011, 15:29

cofi: Baust du vielleicht gerade so etwas ähnliches wie contextlib.nested nach?
deets

Donnerstag 14. Juli 2011, 15:30

@TheGrudge

Du meinst mehre tausend zeilen? Mit mehreren hundert Byte pro Zeile? Das waeren dann ja.... einige hundert Kilobyte! Schockschwerenot! :shock: Ob die wohl in deinen mehrere Gigabyte grossen Hautpspeicher passen? Ich weiss ja nicht....

Aber im Ernst: wenn das wirklich alles ist, dann wuerde ich das einfach im RAM halten. Wenn es doch signifikant mehr ist als ein par MB, dann kann man das auch auch wie cofi vorschlug mit einem Tee-Objekt bauen, habe ich auch schon getan.
Benutzeravatar
cofi
Moderator
Beiträge: 4432
Registriert: Sonntag 30. März 2008, 04:16
Wohnort: RGFybXN0YWR0

Donnerstag 14. Juli 2011, 15:35

derdon hat geschrieben:cofi: Baust du vielleicht gerade so etwas ähnliches wie contextlib.nested nach?
Nein, da fallen mehrere Dateiobjekte raus, aber gerade damit hat man in Verbindung mit `subprocess` nicht so viel Spass. Ich rede von so etwas wie `man tee' ;)
Benutzeravatar
TheGrudge
User
Beiträge: 91
Registriert: Donnerstag 4. Mai 2006, 18:39

Donnerstag 14. Juli 2011, 16:32

deets hat geschrieben: Aber im Ernst: wenn das wirklich alles ist, dann wuerde ich das einfach im RAM halten. Wenn es doch signifikant mehr ist als ein par MB, dann kann man das auch auch wie cofi vorschlug mit einem Tee-Objekt bauen, habe ich auch schon getan.
Das Problem ist nicht unbedingt der Speicher, aber die Laufzeit, der User soll den Output ja live mitlesen können, gleichzeitig wird der Output nochmal als "Backup-Log" in einer Datei gespeichert.

Wenn nun aber subprocess.call() oder dergleichen alles in das cStringIO Objekt umleitet, kann ich doch erst nach dem subprocess.call() Aufruf das cStringIO-Objekt auf die Konsole und in die Datei schreiben, oder irre ich mich jetzt?
Benutzeravatar
cofi
Moderator
Beiträge: 4432
Registriert: Sonntag 30. März 2008, 04:16
Wohnort: RGFybXN0YWR0

Donnerstag 14. Juli 2011, 19:09

Ich hab ein wenig rumgespielt und festgestellt, dass ein primitives Tee fuer `subprocess` nicht ausreicht, da es ueber die Filehandle geht; aus dem selben Grund scheidet auch `StringIO` aus.

Bleibt noch ueber Pipes zu gehen, mal schaun.
Benutzeravatar
cofi
Moderator
Beiträge: 4432
Registriert: Sonntag 30. März 2008, 04:16
Wohnort: RGFybXN0YWR0

Donnerstag 14. Juli 2011, 21:14

Damit ich mir die Arbeit nicht umsonst gemacht hab, hier ist die Tee-Klasse: https://gist.github.com/1083302

Fuer das Problem aber wie gesagt nicht anwendbar, da `subprocess` aus irgendeinem Grund file-descriptors als kleinsten gemeinsamen Nenner nutzt und aus denen dann wieder per `os.fdopen` `file` Objekte macht. Damit werden file-like Objekte effektiv ausgeschlossen.

Fuer das konkrete Problem gibt es dann nur die haessliche Moeglichkeit manuell zu lesen und `subprocess.Popen` statt `subprocess.call` zu nutzen, z.B. hier http://stackoverflow.com/questions/5271 ... is-running

Problematisch ist hier evtl, dass die Reihenfolge nicht mehr stimmt, also `stdout` und `stderr` anders geschrieben als ausgegeben werden.
Benutzeravatar
TheGrudge
User
Beiträge: 91
Registriert: Donnerstag 4. Mai 2006, 18:39

Donnerstag 14. Juli 2011, 22:13

Danke für all eure Antworten. Werde mir mal deine Tee Klasse anschauen und gucken, wie Popen für mich funktioniert...
Benutzeravatar
TheGrudge
User
Beiträge: 91
Registriert: Donnerstag 4. Mai 2006, 18:39

Donnerstag 14. Juli 2011, 22:53

Ich verwende jetzt Popen, funktioniert ganz gut, wohl nicht das performanteste, aber naja...

Code: Alles auswählen

    p = subprocess.Popen("program -p1 -p2".split(), shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)

    with open("program.log", "w") as f:
        while True:
            next_line = p.stdout.readline()
            if next_line == "" and p.poll() != None:
                break
            sys.stdout.write(next_line)
            sys.stdout.flush()
            f.write(next_line)
            f.flush()

            next_line = p.stderr.readline()
            if next_line == "" and p.poll() != None:
                break
            sys.stderr.write(next_line)
            sys.stderr.flush()
            f.write(next_line)
            f.flush()
Funktioniert wohl nicht in Python3, aber wir setzen sowieso noch Python27 ein. Bei Python3 bekomme ich irgeneinen Bytearray zurück...

Man kann das Codebeispiel bestimmt besser machen, aber ich habe nun wenigstens den Effekt, den ich gesucht habe. Rein vom "zugucken" sieht es gar nicht so langsam aus
BlackJack

Freitag 15. Juli 2011, 08:25

@TheGrudge: Das funktioniert so nicht. Du hast da immer die Gefahr einer Verklemmung wenn der Prozess auf einer der beiden Verbindungen mehr ausgibt als in den Zwischenpuffer passt und Du gerade im `readline()` der anderen Verbindung hängst.
Benutzeravatar
TheGrudge
User
Beiträge: 91
Registriert: Donnerstag 4. Mai 2006, 18:39

Freitag 15. Juli 2011, 18:40

Ok, werde mir das noch einmal genauer anschauen, aber im Moment brauche ich die Lösung gar nicht mehr, wir haben uns darauf "geeinigt", dass man "tail" unter Linux oder WinTail unter Windows optional auf das Logfile loslassen kann :D
Antworten