Hier wurde die Ursache nicht geklärt und `os.popen3()` gibt es mittlerweile nicht mehr. Und tatsächlich wurde der Fehler auch gar nicht behoben, denn auch beim umgekehrten auslesen der beiden Dateien, ist nicht garantiert, dass das problemlos funktioniert.
Erst einmal kann ich das konkrete Beispiel selbst mit Python 2.7 nicht mehr nachvollziehen. Das ist nämlich so ein typischer Fehler bei Nebenläufigkeit, wo es von der Umgebung und den konkreten Daten(mengen) abhängt, ob das zufällig noch funktioniert, oder man in eine Verklemmung („deadlock“) läuft und alles hängt, bis man einen der beiden Prozesse von aussen beendet.
Wenn man beide Ausgaben eines externen Prozesses per Pipe abgreift, dann darf man die nicht nacheinander blockierend auslesen, sondern muss die ”gleichzeitig” lesen. Also nicht-blockierende Leseoperationen verwenden, oder so etwas wie `select.select()` um festzustellen bei welche(r|n) Datei(en) gerade Daten anliegen, oder mit einem Thread pro Datei.
Zwischen den beiden Prozessen gibt es nämlich einen Puffer für jede Pipe und wenn der Puffer voll ist, wartet der schreibende Prozess solange bis der lesende Prozess Daten ausgelesen hat und wieder Platz im Puffer ist. Hier wartet das Python-Programm im ersten Beitrag solange auf Ausgaben von ``agrep`` auf `stderr` bis ``agrep`` fertig ist und sein Ende der Pipe schliesst. ``agrep`` hingegen schreibt das Ergebnis der Suche in `stdout`. Der Puffer dort ist dann irgendwann voll und ``agrep`` wartet bis das Python-Programm anfängt davon zu lesen. Und in diesem Zustand warten die beiden Programme dann bis in alle Ewigkeit aufeinander.
Wenn man im Python-Programm erst `stdout` ausliest und dann `stderr` scheint der Fehler behoben, aber es kann auch bei *der* Reihenfolge grundsätzlich das gleiche Problem auftreten. Das ist also keine wirkliche Lösung.
Vor 20 Jahren war der Puffer für Pipes bei Linux 4 KiB gross. Heute ist der grösser und zwar gross genug, dass bei dem konkreten Beispiel aus dem ersten Beitrag scheinbar kein Problem existiert, denn das Ergebnis von dem ``agrep``-Aufruf passt komplett in den Pufferspeicher.
Die Grösse des Pufferspeichers von Pipes kann man ”live” und ziemlich low-level ermitteln, denn Python hat dünne Wrapper über die C-Funktionen um Pipes zu erstellen und davon dann die Grösse abzufragen:
Code: Alles auswählen
In [321]: a, b = os.pipe()
In [322]: fcntl.fcntl(a, fcntl.F_GETPIPE_SZ)
Out[322]: 65536
In [323]: os.close(a); os.close(b)
In [324]: 65536 / 1024
Out[324]: 64.0
Auf aktuellen Linux-Systemen ist der Puffer also nicht mehr 4 KiB sondern 64 KiB gross, was für das komplette Ergebnis von dem ``agrep``-Aufruf locker ausreicht, denn der belegt nicht einmal die Hälfte des Pufferspeichers:
Code: Alles auswählen
$ agrep -ihw 'get' Downloads/ger-eng.txt | wc --bytes
30103
Eine erste Näherung des (fehlerhaften) Programms in Python 3 mit dem `subprocess`-Modul könnte so aussehen:
Code: Alles auswählen
#!/usr/bin/env python3
from subprocess import PIPE, Popen
def main():
with Popen(
"agrep -ihw 'get' Downloads/ger-eng.txt",
shell=True,
stdin=PIPE,
stdout=PIPE,
stderr=PIPE,
) as agrep:
if agrep.stderr.readlines() == []:
print(agrep.stdout.readlines())
if __name__ == "__main__":
main()
Jetzt brauchen wir aber weder die zusätzliche Shell noch `stdin` wirklich. Dafür möchte man vielleicht gerne Zeichenketten statt Bytes haben, also muss man die Kodierung angeben:
Code: Alles auswählen
#!/usr/bin/env python3
from subprocess import PIPE, Popen
from threading import Thread
def main():
with Popen(
["agrep", "-ihw", "get", "Downloads/ger-eng.txt"],
stdout=PIPE,
stderr=PIPE,
encoding="iso-8859-1",
) as agrep:
error_lines = []
thread = Thread(target=lambda: error_lines.extend(agrep.stderr))
thread.start()
lines = list(agrep.stdout)
thread.join()
if not error_lines:
print(lines)
if __name__ == "__main__":
main()
Wenn es okay ist, dass man die Ausgabe als *eine* Zeichenkette bekommt, statt als Liste mit Zeilen, kann man es sich mit `subprocess.run()` einfacher machen:
Code: Alles auswählen
#!/usr/bin/env python3
from subprocess import run
def main():
result = run(
["agrep", "-ihw", "get", "Downloads/ger-eng.txt"],
capture_output=True,
encoding="iso-8859-1",
check=False,
)
if not result.stderr:
print(result.stdout.splitlines(keepends=True))
if __name__ == "__main__":
main()