2017-01-29 1 views
2

Ich habe ein Skript, das farbige Ausgabe druckt, wenn es auf tty ist. Ein Haufen von ihnen wird parallel ausgeführt, so dass ich ihre Stdout nicht zu tty setzen kann. Ich habe auch keine Kontrolle über den Skript-Code (um die Farbe zu erzwingen), also möchte ich es über pty vortäuschen. Mein Code:Lesen von pty ohne endlos hängen

invocation = get_invocation() 
master, slave = pty.openpty() 
subprocess.call(invocation, stdout=slave) 
print string_from_fd(master) 

Und ich kann nicht herausfinden, was in string_from_fd sein sollte. Denn jetzt habe ich so etwas wie

def string_from_fd(fd): 
    return os.read(fd, 1000) 

Es funktioniert, aber diese Zahl 1000 sieht seltsam aus. Ich denke, die Ausgabe kann leise groß sein, und jede Zahl dort könnte nicht ausreichen. Ich habe viele Lösungen aus dem Stapelüberlauf versucht, aber keiner von ihnen funktioniert (es druckt nichts oder hängt für immer).

Ich bin nicht sehr vertraut mit Dateideskriptoren und all das, so dass jede Klärung, wenn ich etwas falsch mache würde sehr geschätzt werden.

Danke!

Antwort

3

Dies funktioniert nicht für lange Ausgänge: subprocess.call wird blockiert, sobald der PTY-Puffer voll ist. Deshalb existiert subprocess.communicate, aber das wird nicht mit einem PTY funktionieren.

Der Standard/einfachste Lösung ist das externe Modul pexpect, die PTYs verwenden intern verwendet: Zum Beispiel

pexpect.spawn("/bin/ls --color=auto").read() 

geben Ihnen die ls Ausgabe mit Farbcodes. Wenn Sie an subprocess bleiben möchten, müssen Sie subprocess.Popen aus dem oben genannten Grund verwenden. Sie haben recht, wenn Sie unter 1000 höchstens 1000 Bytes lesen, also müssen Sie eine Schleife verwenden. os.read blockiert, wenn nichts zu lesen ist und auf Daten wartet. Der Haken ist, wie man erkennt, wann der Prozess beendet wurde: In diesem Fall wissen Sie , dass keine Daten mehr ankommen werden. Der nächste Anruf an os.read wird für immer blockieren. Glücklicherweise hilft Ihnen das Betriebssystem, diese Situation zu erkennen: Wenn alle Dateideskriptoren zum Pseudoterminal, die zum Schreiben verwendet werden könnten, geschlossen sind, gibt os.read entweder eine leere Zeichenfolge zurück oder gibt je nach Betriebssystem einen Fehler zurück. Sie können nach dieser Bedingung suchen und die Schleife beenden, wenn dies geschieht. Jetzt das letzte Stück zum Verständnis des folgenden Codes ist zu verstehen, wie offene Dateideskriptoren und subprocess gehen zusammen: subprocess.Popen intern ruft fork(), die den aktuellen Prozess einschließlich aller offenen Dateideskriptoren dupliziert, und dann innerhalb einer der beiden Ausführungspfade Anrufe exec(), die beendet den laufenden Prozess zugunsten eines neuen. Im anderen Ausführungspfad kehrt die Steuerung zu Ihrem Python-Skript zurück. Also nach dem Aufruf subprocess.Popen gibt es zwei gültige Dateideskriptoren für das Slave-Ende der PTY: Einer gehört zu dem erzeugten Prozess, einer zu Ihrem Python-Skript. Wenn Sie Ihre schließen, gehört der einzige Dateideskriptor, der zum Senden von Daten an das Master-Ende verwendet werden könnte, zum erzeugten Prozess. Nach seiner Beendigung wird es geschlossen, und das PTY tritt in den Zustand ein, in dem Anrufe an read auf dem Master-Ende fehlschlagen.

Hier ist der Code:

import os 
import pty 
import subprocess 

master, slave = pty.openpty() 
process = subprocess.Popen("/bin/ls --color", shell=True, stdout=slave, 
          stdin=slave, stderr=slave, close_fds=True) 
os.close(slave) 

output = [] 
while True: 
    try: 
     data = os.read(master, 1024) 
    except OSError: 
     break 
    if not data: 
     break 
    output.append(data) # In Python 3, append ".decode()" to os.read() 
output = "".join(output) 
+0

hmm, habe ich versucht, und es funktioniert nicht. Es geht einfach "wahr": "für immer. –

+0

Ich habe den 'Popen'-Aufruf so aktualisiert, dass der erzeugte Prozess * nur * den PTY sieht.Wenn dies die Dinge nicht ändert, dann: Welches Betriebssystem benutzen Sie? Sind Sie sicher, dass das Skript, das Sie ausführen möchten, beendet wird/nicht blockiert, weil es Benutzereingaben erwartet? Ist mein Beispiel as-is beendet oder läuft es auch für immer? Können Sie einen Beispielbefehl erstellen, den Sie teilen möchten, wenn die Schleife nicht beendet wird? Mit welcher Python-Version? (Versuchen Sie zu Debugging-Zwecken, einen 'print repr (output [-1])' innerhalb der while-Schleife hinzuzufügen.) – Phillip

+0

Ich verwende OS X 10.12, Python 2.7.10. Selbst mit Ihrem Beispiel, '/ bin/ls --color', bleibt es hängen. 'print repr (Ausgabe [-1])' schreibt endlos '' '' - nur leere Zeichenfolge. –