DUP, DUP2, TMPFile und Stdout in Python
-
27-10-2019 - |
Frage
Dies ist eine Follow -up -Frage von hier.
Wohin ich gehen will
Ich möchte in der Lage sein, den STDOut vorübergehend in eine TEMP -Datei umzuleiten, während Python immer noch in STDOut drucken kann. Dies würde die folgenden Schritte beinhalten:
- Erstellen Sie eine Kopie von stdout (
new
) - Erstellen Sie eine TEMP -Datei (
tmp
) - Stdout in
tmp
- Sagen Sie Python, sie sollen verwenden
new
als Stdout - Umleiten
tmp
in den "echten" Stdout - Sagen Sie Python, dass er den "echten" Stdout erneut verwenden soll
- Lesen und schließen
tmp
Implementierung
Ich habe versucht, die oben genannten folgenden Weise zu implementieren:
import os
import subprocess
import sys
#A function that calls an external process to print to stdout as well as
#a python print to pythons stdout.
def Func(s, p = False):
subprocess.call('echo "{0}"'.format(s), shell = True)
if p:
print "print"
sil = list() # <-- Some list to store the content of the temp files
print "0.1" # Some testing of the
Func("0.2") # functionality
new = os.dup(1) # Create a copy of stdout (new)
tmp = os.tmpfile() # Create a temp file (tmp)
os.dup2(tmp.fileno(), 1) # Redirect stdout into tmp
sys.stdout = os.fdopen(new, 'w', 0) # Tell python to use new as stdout
Func("0.3", True) # <--- This should print "0.3" to the temp file and "print" to stdout
os.dup2(new, 1) # Redirect tmp into "real" stdout
sys.stdout = os.fdopen(1, 'w', 0) # Tell python to use "real" stdout again
# Read and close tmp
tmp.flush()
tmp.seek(0, os.SEEK_SET)
sil.append(tmp.read())
tmp.close()
Ich möchte hier eine kleine Pause einlegen, um zusammenzufassen.
Die Ausgabe zur Konsole bis hierher sollte lautet:
0.1
0.2
print
während sil
sollte so aussehen: ['0.3\n']
. Also funktioniert alles bis hier wie ein Zauber. Wenn ich das Skript oben jedoch wieder so wiederhole:
print "1.1" # Some testing of the
Func("1.2") # functionality
new = os.dup(1) # Create a copy of stdout (new)
tmp = os.tmpfile() # Create a temp file (tmp)
os.dup2(tmp.fileno(), 1) # Redirect stdout into tmp
sys.stdout = os.fdopen(new, 'w', 0) # Tell python to use new as stdout
# This should print "0.3" to the temp file and "print" to stdout and is the crucial point!
Func("1.3", True)
os.dup2(new, 1) # Redirect tmp into "real" stdout
sys.stdout = os.fdopen(1, 'w', 0) # Tell python to use "real" stdout again
# Read and close tmp
tmp.flush()
tmp.seek(0, os.SEEK_SET)
sil.append(tmp.read())
Ein Fehler tritt auf und die Ausgabe sieht so aus:
1.1
1.2
/bin/sh: line 0: echo: write error: Bad file descriptor
print
während sil
liest: ['0.3\n', '']
.
Mit anderen Worten: die zweite Func("1.3", True)
ist nicht in der Lage, in die TEMP -Datei zu schreiben.
Fragen
- Zunächst möchte ich wissen, warum mein Skript nicht so funktioniert, wie ich es möchte. Das heißt, warum ist es in der ersten Hälfte des Skripts nur möglich, in die TEMP -Datei zu schreiben?
- Ich bin immer noch ein wenig verwirrt über die Verwendung von
dup
unddup2
. Während ich denke, ich verstehe, wie die Umleitung von stdout in eine temporäre Datei funktioniert, weiß ich jetzt total, warumos.dup2(new, 1)
Tun, was es tut. Vielleicht könnte die Antwort auf das ausgehen, was alledup
unddup2
s in meinem Skript machen ^^ ^^
Lösung
Der Grund, warum Sie einen "schlechten Dateideskriptor" erhalten, ist, dass der Müllsammler die STDOut -FD für Sie schließt. Betrachten Sie diese beiden Zeilen:
sys.stdout = os.fdopen(1, 'w', 0) # from first part of your script
...
sys.stdout = os.fdopen(new, 'w', 0) # from second part of your script
Wenn nun die zweite dieser beiden ausgeführt wird, fällt die Referenzzahl des ersten Dateiobjekts auf Null und der Müllsammler zerstört es. Dateiobjekte schließen ihre zugehörige FD, wenn sie zerstört werden, und diese FD ist zufällig 1 = stdout. Sie müssen also sehr vorsichtig sein, wie Sie Objekte zerstören os.fdopen
.
Hier ist ein kleines Beispiel, um das Problem zu zeigen. os.fstat
wird nur als Beispielfunktion verwendet, die den Fehler "schlechter Dateideskriptor" auslöst, wenn Sie ihm eine geschlossene FD übergeben.
import os
whatever = os.fdopen(1, 'w', 0)
os.fstat(1)
del whatever
os.fstat(1)
Ich habe tatsächlich einen Kontextmanager, von dem ich denke, dass es genau (oder mindestens mindestens mindestens ich in meinem Fall eine benannte Tempfile benötigt), wonach Sie suchen. Sie können sehen, dass es das ursprüngliche Sys.stdout -Objekt wiederverwendet, um das enge problematische Problem zu vermeiden.
import sys
import tempfile
import os
class captured_stdout:
def __init__(self):
self.prevfd = None
self.prev = None
def __enter__(self):
F = tempfile.NamedTemporaryFile()
self.prevfd = os.dup(sys.stdout.fileno())
os.dup2(F.fileno(), sys.stdout.fileno())
self.prev = sys.stdout
sys.stdout = os.fdopen(self.prevfd, "w")
return F
def __exit__(self, exc_type, exc_value, traceback):
os.dup2(self.prevfd, self.prev.fileno())
sys.stdout = self.prev
##
## Example usage
##
## here is a hack to print directly to stdout
import ctypes
libc=ctypes.LibraryLoader(ctypes.CDLL).LoadLibrary("libc.so.6")
def directfdprint(s):
libc.write(1, s, len(s))
print("I'm printing from python before capture")
directfdprint("I'm printing from libc before captrue\n")
with captured_stdout() as E:
print("I'm printing from python in capture")
directfdprint("I'm printing from libc in capture\n")
print("I'm printing from python after capture")
directfdprint("I'm printing from libc after captrue\n")
print("Capture contains: " + repr(file(E.name).read()))