2012-04-01 22 views
2

Ich versuche, von stdout und stderr von einem Popen zu lesen und sie auszudrucken. Der Befehl, den ich mit Popen leite ist die folgendePopen gibt keine Ausgabe sofort wenn verfügbar

#!/bin/bash 

i=10 
while ((i > 0)); do 
    sleep 1s 
    echo heyo-$i 
    i="$((i-1))" 
done 

echo 'to error' >&2 

Als ich dies in der Shell ausgeführt, ich eine Zeile der Ausgabe zu erhalten und dann eine zweite Pause und dann eine Zeile wieder, usw. Aber ich bin nicht in der Lage zu Erstelle das mit Python neu. Ich starte zwei Threads, jeweils einen von stdout und stderr zu lesen, lege die gelesenen Zeilen in einen Queue und einen anderen Thread, der Elemente aus dieser Warteschlange aufnimmt und ausdruckt. Aber damit sehe ich, dass alle Ausgaben auf einmal ausgedruckt werden, nachdem der Subprozess beendet ist. Ich möchte, dass die Zeilen gedruckt werden, wenn sie echo 'ed sind.

Hier ist meine Python-Code:

# The `randoms` is in the $PATH 
proc = sp.Popen(['randoms'], stdout=sp.PIPE, stderr=sp.PIPE, bufsize=0) 

q = Queue() 

def stream_watcher(stream, name=None): 
    """Take lines from the stream and put them in the q""" 
    for line in stream: 
     q.put((name, line)) 
    if not stream.closed: 
     stream.close() 

Thread(target=stream_watcher, args=(proc.stdout, 'out')).start() 
Thread(target=stream_watcher, args=(proc.stderr, 'err')).start() 

def displayer(): 
    """Take lines from the q and add them to the display""" 
    while True: 
     try: 
      name, line = q.get(True, 1) 
     except Empty: 
      if proc.poll() is not None: 
       break 
     else: 
      # Print line with the trailing newline character 
      print(name.upper(), '->', line[:-1]) 
      q.task_done() 

    print('-*- FINISHED -*-') 

Thread(target=displayer).start() 

Irgendwelche Ideen? Was fehlt mir hier?

+0

Was Sie vermissen ist, dass die Pipeline gepuffert ist. Ich weiß nicht, wie Sie den Bash-Prozess dazu bringen können, den Puffer zu leeren. –

+0

Ich glaube auch nicht, dass Stream Watcher funktioniert. 'for line in stream' wird alle Zeilen auslassen, die anfänglich verfügbar sind, dann schließe den stdout/stderr - ich glaube nicht, dass du sie zuerst schließen willst, und du wartest nicht und checkst noch mehr Zeilen nach erscheinen. – agf

+0

@SvenMarnach, Pipeline wird von Python gepuffert? Wenn ja, wie kann ich sagen, dass ich das nicht tun soll und stattdessen die Daten angeben soll? @agf, die for-Schleife wird nicht enden, bis eine 'StopIteration' ausgelöst wird, was meiner Meinung nach geschieht, wenn der Subprozess seine Seite der Pipe schließt. Deshalb schließe ich den Stream sofort. Außerdem überprüfe ich, ob der Prozess im Displayer-Thread läuft. –

Antwort

4

Nur stderr ist ungepuffert, nicht stdout. Was Sie wollen, kann nicht allein mit den Shell-Einbauten getan werden. Das Pufferverhalten ist in der stdio (3) C-Bibliothek definiert, die Zeilenpufferung nur anwendet, wenn die Ausgabe an ein Terminal erfolgt. Wenn die Ausgabe an eine Pipe erfolgt, wird sie gepuffert, nicht gepuffert, und die Daten werden nicht zum Kernel und von dort zum anderen Ende der Pipe übertragen, bis der Pipe-Puffer gefüllt ist.

Darüber hinaus hat die Shell keinen Zugriff auf libc Puffer-steuernde Funktionen, wie setbuf (3) und Freunde. Die einzige mögliche Lösung innerhalb der Shell besteht darin, Ihren Co-Prozess auf einem Pseudo-Tty zu starten, und Pty Management ist ein komplexes Thema. Es ist viel einfacher, das entsprechende Shell-Skript in einer Sprache neu zu schreiben, die Zugriff auf Low-Level-Pufferfunktionen für Ausgabestreams gewährt, als dafür zu sorgen, dass etwas über eine Pty ausgeführt wird.

Wenn Sie jedoch /bin/echo anstelle der integrierten Shell echo aufrufen, können Sie es mehr nach Ihren Wünschen finden. Dies funktioniert, weil jetzt die gesamte Zeile geleert wird, wenn der neu gestartete Prozess /bin/echo jedes Mal beendet wird. Dies ist kaum eine effiziente Nutzung von Systemressourcen, sondern kann eine effiziente Nutzung Ihrer eigenen sein.

+0

Ausgezeichnete Antwort. Vielen Dank. Ja, ich möchte nicht gehen, obwohl ich eine Pty verwaltet. Ich wusste nicht, dass die Pufferung von stdio davon abhängt, auf welches Ziel geschrieben wird. Das ist eine sehr nützliche Information. –

+1

'expect' enthält ein' unbuffer (1) 'Dienstprogramm, das den Pty Dance ausführt, um die Pufferung von stdio zu deaktivieren. Also könnte das OP einfach das 'randoms'-Skript in ein anderes Skript einpacken, das einfach (warning: ungested)' randoms | unbuffer -p'. – ninjalj

+0

Ich habe viele Ansätze ausprobiert, wie zB '' '' fcntl.fcntl (..., os.O_NONBLOCK) '' stdout' ', aber die einzige Lösung, die mir geholfen hat, wurde [hier] beschrieben (https://stackoverflow.com/a/5413588/676369). Kurz gesagt, die Lösung besteht darin, mit 'pty.openpty()' ein nicht-blockierendes 'stdout' zu machen – Exterminator13

-1

IIRC, Einstellung shell=True auf Popen sollte es tun.

+0

Nein, habe ich nicht gemacht. Mit diesem Argument änderte ich natürlich '['randoms']' in '' randoms' '. Aber, ja, ich sehe keine Ausgabe, bis der Subprozess beendet ist. –

+0

@ShrikantSharat: Wenn du 'expect' installiert hast, solltest du' shell = True' setzen und 'randoms | starten können unbuffer -p'. – ninjalj

+0

@ninjalj, Das ist ein nützliches Werkzeug. Vielen Dank. Könnten Sie das als Antwort für die Aufzeichnung verwenden? –

Verwandte Themen